/**
    receiving side of network protocol, including main loop and UDP packet handling

    Copyright (c) 2020-2023 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 "keygen.h"
#include "network.h"
#include "mainline.h"
#include "contact.h"
#include "param.h"
#include "proto.h"
#include "limit.h"
#include "proxy.h"
#include "proxies.h"
#include "nat.h"
#include "server.h"
#include "client.h"
#include "msg.h"
#include "xfer.h"
#include "audio.h"
#include "api.h"

#ifndef _WIN32
#include <unistd.h>

#ifndef __USE_XOPEN_EXTENDED
#define __USE_XOPEN_EXTENDED /* srandom */
#endif

#include <netinet/in.h>
#include <arpa/inet.h>
#else
#undef EADDRINUSE
#define EADDRINUSE WSAEADDRINUSE
#endif

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

#define SIM_MODULE "server"

static const char *audio_qos_key_names[] = {
  SIM_CMD_PONG_AUDIO_READ, SIM_CMD_PONG_AUDIO_WRITE, SIM_CMD_PONG_AUDIO_ALL, SIM_CMD_PONG_AUDIO_LOST
};

const char *server_flag_names[7] = { "msg", "proxy", "connect", NULL, "verify", "verify-ok", "verify-nok" };

static pth_t tid_https, tid_tcp, tid_udp;
static struct _socket server_tcp_sock, server_https_sock;

static simnumber server_audio_ping_tick = 0, server_audio_pong_tick = 0, server_audio_pong_value;

static int server_send_duplicate (simclient client, simsocket sock, unsigned ip, simnumber port) {
  LOG_INFO_ ("duplicate $%d:$%d ip %s port %lld '%s'\n", client->sock->fd, sock->fd,
             network_convert_ip (ip), port, client->contact->nick.str);
  return client_send_cmd (client, SIM_CMD_DUPLICATE, SIM_CMD_DUPLICATE_IP, string_copy (network_convert_ip (ip)),
                          SIM_CMD_DUPLICATE_PORT, number_new (port));
}

static void server_set_hosts (simcontact contact, const simtype hosts, int port) {
  unsigned i, j, k;
  for (i = j = 1; i <= hosts.len; i++) {
    simtype host = sim_convert_string_to_locase ((char *) (hosts.arr[i].str + (hosts.arr[i].str[0] == '.')));
    if (hosts.arr[i].str[0] != '.') {
      if (port && contact_find_host (contact, host.ptr, port)) {
        contact_add_host (contact, host.ptr, port);
      } else if (SIM_CHECK_SHORT (port) && host.len && host.len <= SIM_MAX_HOST_LENGTH && j++ <= SIM_MAX_HOST_COUNT)
        contact_probe (contact, 0, port, host.ptr, CONTACT_PROBE_RESOLVE);
    } else if ((k = contact_find_host (contact, host.ptr, 0)) != 0)
      array_delete (&contact->hosts, k, 1);
    string_free (host);
  }
}

int server_set_status (simcontact contact, simclient client, const simtype version,
                       simunsigned age, simunsigned myage) {
  const char **names = &CONTACT_LOOKUP_STATUS_NAME (SIM_STATUS_OFF);
  const unsigned len = SIM_STATUS_MAX - SIM_STATUS_OFF + 1;
  simtype array = table_get_array_string (version, SIM_CMD_STATUS_RIGHTS);
  simnumber flags = sim_convert_strings_to_flags (array, SIM_ARRAY (contact_right_names));
  simtype str = table_get_string (version, SIM_CMD_STATUS_STATUS);
  simtype nick = table_get_string (version, SIM_CMD_STATUS_NICK);
  simtype line = table_get_string (version, SIM_CMD_STATUS_LINE);
  int err = SIM_OK, status = sim_convert_string_to_enum (str.ptr, SIM_STATUS_OFF, SIM_STATUS_ON, names, len);
  contact_set_nick (contact, nick, line, table_get_number (version, SIM_CMD_STATUS_EDIT));
  if (contact == contact_list.me && myage && simself.status != SIM_STATUS_OFF) {
    static const char *server_mode_names[] = { "incoming", "outgoing" };
    simbool mode = table_get_type_number (version, SIM_REPLY).typ != SIMNIL;
    if (age && myage > age) {
      simnumber sec = time (NULL) - age / 1000;
      event_send_history_system (contact, "STATUS ", "OFF", 0, sec < 0 ? 0 : sec, SIM_OK, mode);
      err = SIM_SERVER_OFFLINE;
    } else if (age || status != SIM_STATUS_OFF) {
      simnumber sec = time (NULL) - age / 1000;
      event_send_history_system (contact, "STATUS ", "ON", 0, sec < 0 ? 0 : sec, SIM_OK, mode);
    }
    LOG_NOTE_ ("status %s forced by %s (age = %llu, myage = %llu)\n",
               err == SIM_OK ? "ON" : "OFF", server_mode_names[mode], age, myage);
  }
  if (! client || ! (client->flags & CLIENT_FLAG_CONNECTED))
    flags = (flags & ~CONTACT_FLAG_TYPE) | (contact->flags & CONTACT_FLAG_TYPE);
  contact_set_status (contact, str.typ != SIMNIL ? status : contact->status, flags);
  if (client && ! (client->flags & CLIENT_FLAG_CONNECTED))
    client->xfer.cid = table_get_number (version, client->flags & CLIENT_FLAG_ACCEPTED ? SIM_REQUEST_ID : SIM_REPLY_ID);
  contact->seen[CONTACT_SEEN_STATUS] = time (NULL);
  return err;
}

static void server_set_ip (simcontact contact, simsocket sock, unsigned ip,
                           unsigned proxyip, simnumber port, int flags) {
  LOG_XTRA_ ("country $%d %s '%s'\n", sock->fd, network_convert_ip (ip), contact->nick.str);
  if (ip)
    contact->location = ip;
  if (! proxyip)
    proxyip = ip;
  if (! proxyip || (! SIM_CHECK_SHORT (port) && port)) {
    LOG_WARN_ ("invalid $%d port %lld ip %s '%s'\n", sock->fd, port, network_convert_ip (proxyip), contact->nick.str);
    port = 0;
  } else if (sock->client && port)
    sock->client->proxyip = proxyip, sock->client->proxyudport = sock->client->proxyport = (int) port;
  if (! (flags & SIM_REQUEST_FLAG_VERIFY) || contact->auth < CONTACT_AUTH_ACCEPTED)
    if (port && contact_set_ip (contact, proxyip, (int) port, CONTACT_IP_UPDATE))
      return;
  if (contact->auth >= CONTACT_AUTH_ACCEPTED) {
    LOG_DEBUG_ ("ip $%d = %s:%lld '%s'\n", sock->fd, network_convert_ip (proxyip), port, contact->nick.str);
    if (port) {
      if (contact != contact_list.me) {
        contact_probe (contact, proxyip, (int) port, NULL, CONTACT_PROBE_HANDSHAKE);
      } else
        contact_set_ip (contact, proxyip, (int) port, CONTACT_IP_ADD);
    }
  } else if (port) {
    LOG_DEBUG_ ("ip $%d += %s:%lld '%s'\n", sock->fd, network_convert_ip (proxyip), port, contact->nick.str);
    contact_set_ip (contact, proxyip, (int) port, CONTACT_IP_ADD);
  }
}

static int server_set (simcontact contact, simsocket sock, unsigned ip, const simtype version, simunsigned myage) {
  unsigned proxyip = sim_network_parse_ip (table_get_pointer (version, SIM_REQUEST_PROXY_IP));
  simnumber proxyport = table_get_number (version, SIM_REQUEST_PROXY_PORT);
  simtype ports, array = table_get_array_string (version, SIM_REQUEST_FLAGS);
  int err = SIM_OK, flags = (int) sim_convert_strings_to_flags (array, SIM_ARRAY (server_flag_names));
  if (! proxyport && (ports = table_get_array_number (version, SIM_REQUEST_PORT)).len)
    proxyport = ports.arr[1].num;
  if (sock->client)
    sock->client->flags |= flags & (SIM_REPLY_FLAG_VERIFY_OK | SIM_REPLY_FLAG_VERIFY_NOK);
  server_set_ip (contact, sock, ip, proxyip, proxyport, flags);
  if (sock->client || ! contact->client) {
    err = server_set_status (contact, sock->client, version, table_get_number (version, SIM_REQUEST_AGE), myage);
  } else if (proxyport)
    server_send_duplicate (contact->client, sock, proxyip ? proxyip : ip, proxyport);
  msg_recv_ack (contact, table_get_number (version, SIM_REQUEST_ACK));
  return err;
}

void server_set_qos (simsocket sock, int tos) {
  simcustomer proxy;
  if (! socket_check_server (sock)) {
    socket_set_qos (sock, NULL, tos);
  } else if ((proxy = proxy_get_proxy (NULL, NULL, NULL)) != NULL)
    socket_set_qos (proxy->sock, NULL, tos);
}

void server_set_ciphers (simclient client) {
  simbool decryptpreferred = false, encryptpreferred = false;
  if (client->insecure.typ == SIMNIL) {
    event_send_name (client->contact, SIM_EVENT_ERROR, SIM_EVENT_ERROR_SYSTEM, nil ());
  } else if (! client->contact->insecure || strcmp (client->insecure.ptr, client->contact->insecure))
    event_send_name (client->contact, SIM_EVENT_ERROR, SIM_EVENT_ERROR_SYSTEM, pointer_new (client->insecure.ptr));
  sim_free_string (client->contact->insecure);
  client->contact->insecure = client->insecure.typ != SIMNIL ? string_copy (client->insecure.ptr).ptr : NULL;
  crypt_cipher_find (client->sock->crypt.rcvcipher, 0, &decryptpreferred);
  crypt_cipher_find (client->sock->crypt.sndcipher, 0, &encryptpreferred);
  if (decryptpreferred && encryptpreferred) {
    event_send_name (client->contact, SIM_EVENT_ERROR, SIM_EVENT_ERROR_CIPHER, nil ());
  } else if (! client->contact->rcvcipher || strcmp (client->sock->crypt.rcvcipher, client->contact->rcvcipher) ||
             ! client->contact->sndcipher || strcmp (client->sock->crypt.sndcipher, client->contact->sndcipher)) {
    simtype cipher = pointer_new (client->sock->crypt.rcvcipher);
    if (strcmp (client->sock->crypt.rcvcipher, client->sock->crypt.sndcipher) && ! encryptpreferred)
      cipher = string_concat (cipher.ptr, ":", client->sock->crypt.sndcipher, NULL);
    event_send_name (client->contact, SIM_EVENT_ERROR, SIM_EVENT_ERROR_CIPHER, cipher);
  }
  client->contact->rcvcipher = client->sock->crypt.rcvcipher;
  client->contact->sndcipher = client->sock->crypt.sndcipher;
}

void server_set_versions (simclient client, simcontact contact, simtype versions) {
  if (versions.len) {
    if (contact_list.test & 0x200000) {
      ARRAY_FREE (&contact->versions, versions), versions = nil ();
    } else if (client)
      STRING_FREE (&client->insecure, array_detach (versions, 1));
  }
  array_free (versions);
}

void server_add_versions (simtype table, simcontact contact, int protover,
                          simunsigned nonce, simunsigned myage, simbool oob, simbool server) {
  const char *os;
  table_add_number (table, server ? SIM_REPLY : SIM_REQUEST, protover);
  table_add_number (table, server ? SIM_REPLY_RANDOM : SIM_REQUEST_RANDOM, nonce);
  if (myage)
    table_add_number (table, server ? SIM_REPLY_AGE : SIM_REQUEST_AGE, myage);
  table_add_number (table, server ? SIM_REPLY_ACK : SIM_REQUEST_ACK, contact->msgs.msgnum);
  if (contact == contact_list.system) {
    int i = 1;
    simtype versions = sim_list_versions (), array = array_new_strings (table_count (versions) * 2), key, val;
    simwalker ctx;
    for (table_walk_first (&ctx, versions); (val = table_walk_next_string (&ctx, &key)).typ != SIMNIL; i += 2) {
      array.arr[i] = string_copy_string (key);
      array.arr[array.len = i + 1] = string_copy_string (val);
    }
    table_add (table, server ? SIM_REPLY_BAD : SIM_REQUEST_BAD, array);
    table_free (versions);
  } else if (! oob && (os = sim_system_get_version (NULL, NULL)) != NULL)
    table_add_pointer (table, server ? SIM_REPLY_BAD : SIM_REQUEST_BAD, os);
}

void server_add_ip (simtype table, simsocket sock, simbool oob, simbool server) {
  if (param_get_number ("net.tor.port") <= 0 && ! (contact_list.test & 0x400000)) {
    unsigned ownip = 0, localip = 0, netip, myip = simself.ipin;
    int mainport = abs (param_get_number ("main.port")), netport = network_get_port (&netip), i = 0;
    simtype ports = array_new_numbers (4);
    ports.arr[4] = ports.arr[3] = ports.arr[2] = ports.arr[1] = number_new (netport ? netport : mainport);
    if (! oob) {
      simtype ips = array_new_strings (4);
      if (sock) {
        table_add_number (table, server ? SIM_REPLY_LOCAL_PORT : SIM_REQUEST_LOCAL_PORT, mainport);
        proxy_get_ip (sock, &ownip, &localip);
        if (! server)
          socket_get_addr (sock, &localip, NULL);
        if (ownip && (ownip != localip || param_get_number ("nat.local")))
          ips.arr[++i] = string_copy (network_convert_ip (ownip));
      }
      if (netport && netip && netip != ownip)
        ips.arr[++i] = string_copy (network_convert_ip (netip));
      if (simself.ipout && simself.ipout != localip && simself.ipout != ownip && simself.ipout != netip)
        ips.arr[++i] = string_copy (network_convert_ip (simself.ipout));
      if (myip && myip != localip && myip != ownip && myip != netip && myip != simself.ipout)
        ips.arr[++i] = string_copy (network_convert_ip (myip));
      if ((ips.len = i) != 0) {
        table_add (table, ! sock ? SIM_SERVER_CMD_HANDSHAKE_PROXY_IP : server ? SIM_REPLY_IP : SIM_REQUEST_IP, ips);
      } else
        array_free (ips);
      if (localip && param_get_number ("nat.local")) {
        const char *key = server ? SIM_REPLY_LOCAL_IP : SIM_REQUEST_LOCAL_IP;
        table_add (table, key, string_copy (network_convert_ip (localip)));
      }
    }
    ports.len = i ? i : 1;
    table_add (table, ! sock ? SIM_SERVER_CMD_HANDSHAKE_PROXY_PORT : server ? SIM_REPLY_PORT : SIM_REQUEST_PORT, ports);
  }
}

static int server_probe (simcontact contact, simclient client, const char *ipstr, simnumber port, int probe) {
  unsigned ip = sim_network_parse_ip (ipstr);
  if (ip && SIM_CHECK_SHORT (port))
    return contact_probe (contact, ip, (int) port, NULL, probe);
  LOG_WARN_ ("invalid $%d %s ip %s port %lld '%s'\n", client->sock->fd,
             probe == CONTACT_PROBE_VERIFY ? "VERIFY" : "DROP", ipstr, port, client->contact->nick.str);
  return SIM_SOCKET_BAD_PORT;
}

static int server_exec_invalid (simclient client, const simtype table) {
  LOG_INFO_ ("unknown command $%d: %s '%s'\n", client->sock->fd,
             table_get_string (table, SIM_CMD).str, client->contact->nick.str);
  return SIM_OK;
}

static int server_exec_bye (simclient client, const simtype table) {
  return SIM_SERVER_EXIT;
}

int server_exec_ping (simclient client, const simtype table) {
  simtype pong = client_new_cmd (SIM_CMD_PONG, SIM_CMD_PING_TICK, number_new (system_get_tick ()), NULL, nil ()), ping;
  if (client == audio_status.client || ! table_count (table)) {
    int i, j;
    for (i = j = 0; j <= CONTACT_STAT_COUNT; j++)
      i += client->contact->stats[CONTACT_STATS_MINE][j] != 0;
    if (i)
      for (j = 0; j <= CONTACT_STAT_COUNT; j++)
        table_add_number (pong, audio_qos_key_names[j], client->contact->stats[CONTACT_STATS_MINE][j]);
  }
  if ((ping = table_get_type_number (table, SIM_CMD_PING_PONG)).typ != SIMNIL)
    table_add (pong, SIM_CMD_PING_PONG, ping);
  if ((ping = table_get_type_number (table, SIM_CMD_PING_PONG_NUMBER)).typ != SIMNIL)
    table_add (pong, SIM_CMD_PING_PONG_NUMBER, ping);
  if ((ping = table_get_string (table, SIM_CMD_PONG_PAD)).typ != SIMNIL)
    table_add (pong, SIM_CMD_PONG_PAD, string_copy_string (ping));
  if (! (contact_list.test & 0x10000))
    return client_send (client, pong);
  table_free (pong);
  return SIM_OK;
}

static int server_exec_pong_ (simclient client, const simtype table) {
  int err, j;
  simnumber tick = client->pongtick = system_get_tick ();
  simtype event = table_new_name (2, SIM_EVENT_NET), pong = table_get_type_number (table, SIM_CMD_PING_PONG);
  client->typingping = 0;
  if (pong.typ != SIMNIL && pong.num) {
    pong = number_new (tick - client->sock->created + 1 - pong.num);
    if (tick - client->sock->created < 0 || pong.num < 0)
      pong = number_new (-1);
    pong = number_new (pong.num + 1);
    if (! client->pong)
      client->pong = pong.num;
  }
  for (j = 0; j <= CONTACT_STAT_COUNT; j++) {
    simtype number = table_get_type_number (table, audio_qos_key_names[j]);
    if (number.typ != SIMNIL)
      client->contact->stats[CONTACT_STATS_PEER][j] = number.num;
  }
  table_add_pointer (event, SIM_EVENT_NET, SIM_EVENT_NET_PONG);
  if (pong.typ != SIMNIL)
    table_add (event, SIM_EVENT_NET_PONG, pong);
  if ((pong = table_get_type_number (table, SIM_CMD_PING_PONG_NUMBER)).typ != SIMNIL)
    table_add (event, SIM_EVENT_NET_PONG_NUMBER, pong);
  event_send (client->contact, event);
  if ((err = limit_ping_ (client->sock, client->contact, tick, 0)) == SIM_NO_ERROR) {
    client->sock->pinged = tick;
    err = SIM_OK;
  } else if (err == SIM_LIMIT_NO_ERROR)
    err = SIM_OK;
  return err;
}

static int server_exec_status (simclient client, const simtype table) {
  int err = SIM_OK;
  simnumber flags = client->contact->flags;
  simtype hosts = table_get_array_string (table, SIM_CMD_STATUS_HOSTS);
  if (hosts.typ != SIMNIL && client->contact->auth >= CONTACT_AUTH_ACCEPTED) {
    if (hosts.len) {
      server_set_hosts (client->contact, hosts, client->param.port[1]);
    } else
      contact_add_host (client->contact, NULL, 0);
  }
  server_set_status (client->contact, client, table, 0, 0);
  if (! (flags & CONTACT_FLAG_XFER))
    err = xfer_send_pause (client->contact, 0);
  if (client->contact->msgs.flags & CONTACT_MSG_NOTPUT && client->contact->flags & CONTACT_FLAG_UTF)
    err = SIM_SERVER_RECONNECT;
  return err;
}

static int server_exec_ack (simclient client, const simtype table) {
  msg_recv_ack (client->contact, table_get_number (table, SIM_CMD_ACK_HANDLE));
  return SIM_OK;
}

static int server_exec_msg (simclient client, const simtype table) {
  msg_recv_ack (client->contact, table_get_number (table, SIM_CMD_MSG_ACK));
  return msg_recv (client, table_detach_string (*(simtype *) &table, SIM_CMD_MSG_TEXT), table);
}

static int server_exec_xfer_init (simclient client, const simtype table) {
  simnumber n = table_get_number (table, SIM_CMD_XFER_INIT_NAME);
  simnumber m = table_get_number (table, SIM_CMD_XFER_INIT_NAMES);
  return xfer_recv_pause (client, table_get_number (table, SIM_CMD_XFER_INIT_PAUSE), n, m);
}

static int server_exec_xfer_send (simclient client, const simtype table) {
  simnumber handle = table_get_number (table, SIM_CMD_XFER_SEND_HANDLE);
  simnumber sendtime = table_get_number (table, SIM_CMD_XFER_SEND_TIME);
  simnumber filesize = table_get_number (table, SIM_CMD_XFER_SEND_SIZE);
  simtype name = table_get_string (table, SIM_CMD_XFER_SEND_NAME);
  unsigned len = name.len;
  string_free (sim_convert_utf (name.ptr, &len));
  if (! (name.typ != SIMNIL ? len : handle))
    return SIM_OK;
  if (client->contact->msgs.flags & CONTACT_MSG_OVERFLOW || ! CONTACT_CHECK_RIGHT_XFER (client->contact))
    return client->contact->msgs.flags & CONTACT_MSG_OVERFLOW ? SIM_MSG_OVERFLOW : SIM_API_NO_RIGHT;
  return xfer_recv_file (client, handle, name, filesize, table_get_string (table, SIM_CMD_XFER_SEND_TYPE), sendtime);
}

static int server_exec_xfer_recv (simclient client, const simtype table) {
  simnumber hashsize = table_get_number (table, SIM_CMD_XFER_RECV_SIZE);
  simtype hash = table_get_string (table, SIM_CMD_XFER_RECV_HASH);
  if (hash.typ == SIMNIL && SIM_CHECK_NEGATIVE (hashsize) && hashsize != SIM_SOCKET_NO_ERROR)
    return xfer_recv_handle (client, table_get_number (table, SIM_CMD_XFER_RECV_HANDLE), hashsize);
  if (client->contact->msgs.flags & CONTACT_MSG_OVERFLOW || ! CONTACT_CHECK_RIGHT_XFER (client->contact))
    return client->contact->msgs.flags & CONTACT_MSG_OVERFLOW ? SIM_MSG_OVERFLOW : SIM_API_NO_RIGHT;
  return xfer_accept_file (client, table_get_number (table, SIM_CMD_XFER_RECV_HANDLE), hash, hashsize);
}

static int server_exec_xfer_data_ (simclient client, const simtype table) {
  return xfer_recv_data_ (client, table_get_string (table, SIM_CMD_XFER_DATA_BYTES));
}

static int server_exec_xfer_offset (simclient client, const simtype table) {
  simnumber speed = table_get_number (table, SIM_CMD_XFER_OFFSET_SPEED);
  xfer_recv_speed (client, table_get_number (table, SIM_CMD_XFER_OFFSET_SIZE), speed);
  return SIM_OK;
}

static int server_exec_xfer_close (simclient client, const simtype table) {
  simnumber handle = table_get_number (table, SIM_CMD_XFER_CLOSE_HANDLE);
  simtype error = table_get_type_number (table, SIM_CMD_XFER_CLOSE_ERROR);
  if (error.typ != SIMNIL) {
    simnumber filetime = table_get_number (table, SIM_CMD_XFER_CLOSE_TIME);
    int err = SIM_CHECK_NEGATIVE (error.num) ? (int) error.num : SIM_NO_ERROR;
    return xfer_recv_close (client, handle, table_get_string (table, SIM_CMD_XFER_CLOSE_HASH), filetime, err);
  }
  return xfer_recv_handle (client, handle, SIM_SOCKET_NO_ERROR);
}

static int server_exec_xfer_end (simclient client, const simtype table) {
  simnumber handle = table_get_number (table, SIM_CMD_XFER_END_HANDLE);
  simnumber error = table_get_number (table, SIM_CMD_XFER_END_ERROR);
  return xfer_recv_end (client, handle, SIM_CHECK_NEGATIVE (error) ? (int) error : SIM_NO_ERROR);
}

static int server_exec_call_ (simclient client, const simtype table) {
  int err = SIM_API_NO_RIGHT;
  if (client->contact->msgs.flags & CONTACT_MSG_OVERFLOW) {
    err = SIM_MSG_OVERFLOW;
  } else if (CONTACT_CHECK_RIGHT_AUDIO (client->contact))
    err = audio_ring_ (client, table);
  return err;
}

static int server_exec_ring (simclient client, const simtype table) {
  simtype sample = table_get_type_number (table, SIM_CMD_RING_SAMPLE);
  if (sample.typ != SIMNIL) {
    LOG_DEBUG_ ("audio $%d %s %lldHz (quality = %lld) %lld+%lld+%lld ms\n", client->sock->fd,
                ! sample.num ? "ANSWER" : sample.num > 0 ? "RESET" : "REOPEN", sample.num,
                table_get_number (table, SIM_CMD_RING_QUALITY), table_get_number (table, SIM_CMD_RING_FRAMES),
                table_get_number (table, SIM_CMD_RING_LATENCY), table_get_number (table, SIM_CMD_RING_LATENCY_ADD));
  } else if (client->call.state == AUDIO_CALL_OUTGOING) {
    client->flags |= CLIENT_FLAG_RINGING;
    event_send_audio (client->contact, CONTACT_AUDIO_OUTGOING, CONTACT_AUDIO_RINGING);
  }
  return SIM_OK;
}

static int server_exec_hangup_ (simclient client, const simtype table) {
  simnumber error = table_get_number (table, SIM_CMD_HANGUP_ERROR);
  simtype tmp = table_new (1);
  int err = server_exec_ping (client, tmp);
  int err2 = audio_stop_ (client, SIM_CHECK_NEGATIVE (error) ? (int) error : SIM_NO_ERROR, false);
  table_free (tmp);
  event_test_error_audio (client->contact->id, err2);
  return err;
}

static int server_exec_request_call_ (simclient client, const simtype table) {
  return audio_reopen_ (client, table_get_number (table, SIM_CMD_REQUEST_CALL_SAMPLE), false);
}

static int server_exec_reply_call_ (simclient client, const simtype table) {
  return audio_reopen_ (client, table_get_number (table, SIM_CMD_REPLY_CALL_SAMPLE), true);
}

static int server_exec_audio (simclient client, simtype arrays) {
  if (arrays.len == SIM_CMD_AUDIO_NUMBERS && arrays.arr[SIM_CMD_AUDIO_DATA].typ == SIMARRAY_STRING) {
    simtype array = arrays.arr[SIM_CMD_AUDIO_NUMBERS];
    if (array.typ == SIMARRAY_NUMBER && array.len) {
      simnumber pong;
      if (audio_status.client == client && arrays.arr[SIM_CMD_AUDIO_DATA].len) {
        audio_recv_packet (array_detach (arrays.arr[SIM_CMD_AUDIO_DATA], 1), array.arr[SIM_CMD_AUDIO_NUMBERS_TIME].num);
        if (array.len >= SIM_CMD_AUDIO_NUMBERS_PING && (pong = array.arr[SIM_CMD_AUDIO_NUMBERS_PING].num) != 0) {
          server_audio_pong_tick = system_get_tick ();
          server_audio_pong_value = pong;
        }
      }
      if (array.len >= SIM_CMD_AUDIO_NUMBERS_PONG && (pong = array.arr[SIM_CMD_AUDIO_NUMBERS_PONG].num) != 0) {
        simnumber latency, tick = system_get_tick ();
        simnumber proxy = array.len >= SIM_CMD_AUDIO_NUMBERS_PROXY ? array.arr[SIM_CMD_AUDIO_NUMBERS_PROXY].num : 0;
        latency = array.len >= SIM_CMD_AUDIO_NUMBERS_LATENCY ? array.arr[SIM_CMD_AUDIO_NUMBERS_LATENCY].num : 0;
        if (tick - client->sock->created < 0 || (pong = tick - client->sock->created + 1 - pong) < 0) {
          pong = 0;
        } else
          client->latency[CLIENT_PONG_CLIENT] = ++pong;
        if (latency)
          client->latency[CLIENT_PING_CLIENT] = latency;
        if (proxy)
          client->latency[CLIENT_PING_PROXY] = proxy;
        event_send_name_number (client->contact, SIM_EVENT_NET, SIM_EVENT_NET_QOS, pong);
      }
    }
  }
  array_free (arrays);
  return SIM_OK;
}

static int server_exec_udp (simclient client, const simtype table) {
  simnumber port = table_get_number (table, SIM_CMD_UDP_SRC_PORT);
  LOG_DEBUG_ ("udp $%d port = %lld\n", client->sock->fd, port);
  if (SIM_CHECK_SHORT (port) && ! client->call.udport) {
    client->call.udport = (int) port;
    if (client == audio_status.client && audio_status.udport && ! client->sock->udport && ! nat_get_connected (client))
      audio_start_udp (false);
  }
  return SIM_OK;
}

static int server_exec_reverse (simclient client, const simtype table) {
  int err = nat_reverse_exec (client, NAT_CMD_REVERSE);
  if (err != SIM_NAT_TRAVERSE_ATTEMPT)
    return SIM_OK;
  return client_send_cmd (client, SIM_CMD_REVERSEND, NULL, nil (), NULL, nil ());
}

static int server_exec_reversing (simclient client, const simtype table) {
  int err = nat_reverse_exec (client, NAT_CMD_REVERSING);
  if (err != SIM_NAT_TRAVERSE_ATTEMPT)
    return SIM_OK;
  return client_send_cmd (client, SIM_CMD_REVERSEND, NULL, nil (), NULL, nil ());
}

static int server_exec_reversend (simclient client, const simtype table) {
  nat_reverse_exec (client, NAT_CMD_REVERSEND);
  return SIM_OK;
}

static int server_exec_reversed_ (simclient client, const simtype table) {
  int err = nat_reverse_succeed_ (client, false);
  return err == SIM_OK ? SIM_LIMIT_NO_ERROR : err;
}

static int server_exec_traverse_init (simclient client, const simtype table) {
  nat_traverse_attempt (client);
  return SIM_OK;
}

static int server_exec_traverse_start (simclient client, const simtype table) {
  return nat_traverse_exec (client, table, NAT_CMD_START);
}

static int server_exec_traverse_restart (simclient client, const simtype table) {
  return nat_traverse_exec (client, table, NAT_CMD_RESTART);
}

static int server_exec_traverse_inverse (simclient client, const simtype table) {
  return nat_traverse_exec (client, table, NAT_CMD_INVERSE);
}

static int server_exec_traverse_inversed (simclient client, const simtype table) {
  return nat_traverse_exec (client, table, NAT_CMD_INVERSED);
}

static int server_exec_traverse_reply (simclient client, const simtype table) {
  return nat_traverse_exec (client, table, NAT_CMD_NONE);
}

static int server_exec_traversed_ (simclient client, const simtype table) {
  int err = nat_traverse_succeed_ (client, false);
  return err == SIM_OK ? SIM_LIMIT_NO_ERROR : err;
}

static int server_exec_duplicate (simclient client, const simtype table) {
  server_probe (contact_list.me, client, table_get_pointer (table, SIM_CMD_DUPLICATE_IP),
                table_get_number (table, SIM_CMD_DUPLICATE_PORT), CONTACT_PROBE_DUPLICATE);
  return SIM_OK;
}

static int server_exec_request_verify (simclient client, const simtype table) {
  simtype array;
  int err, flag = SIM_REPLY_FLAG_VERIFY_NOK;
  if (server_probe (client->contact, client, table_get_pointer (table, SIM_CMD_REQUEST_VERIFY_IP),
                    table_get_number (table, SIM_CMD_REQUEST_VERIFY_PORT), CONTACT_PROBE_VERIFY) == SIM_OK)
    if (! (contact_list.test & 0x20000))
      flag = simself.flags & SIM_STATUS_FLAG_TCP_OUT ? SIM_REPLY_FLAG_VERIFY_OK : 0;
  array = sim_convert_flags_to_strings (flag, SIM_ARRAY (server_flag_names));
  err = client_send_cmd (client, SIM_CMD_REPLY_VERIFY, array.len ? SIM_CMD_REPLY_VERIFY_FLAGS : NULL,
                         array.len ? array_detach (array, 1) : nil (), NULL, nil ());
  array_free (array);
  return err;
}

static int server_exec_reply_verify (simclient client, const simtype table) {
  simtype array = table_get_array_string (table, SIM_CMD_REPLY_VERIFY_FLAGS);
  int flags = (int) sim_convert_strings_to_flags (array, SIM_ARRAY (server_flag_names));
  client->flags &= ~(SIM_REPLY_FLAG_VERIFY_OK | SIM_REPLY_FLAG_VERIFY_NOK);
  client->flags |= flags & (SIM_REPLY_FLAG_VERIFY_OK | SIM_REPLY_FLAG_VERIFY_NOK);
  LOG_DEBUG_ ("verify $%d%s '%s'\n", client->sock->fd,
              flags & SIM_REPLY_FLAG_VERIFY_OK ? " yes" : flags & SIM_REPLY_FLAG_VERIFY_NOK ? " no" : "",
              client->contact->nick.str);
  return SIM_OK;
}

static int server_exec_request_proxy (simclient client, const simtype table) {
  int err = SIM_OK, port;
  unsigned ip = sim_network_parse_ip (table_get_pointer (table, SIM_CMD_REQUEST_PROXY_IP)), i;
  simtype ports = table_get_type_number (table, SIM_CMD_REQUEST_PROXY_PORT);
  if (ports.typ != SIMNIL) {
    if (! socket_check_client (client->sock) && SIM_CHECK_SHORT (ports.num)) {
      simcustomer local = NULL;
      if (! ip && param_get_number ("server.reverse")) {
        if ((local = socket_check_server (client->sock)) == NULL) {
          socket_get_peer (client->sock, &ip, NULL);
        } else
          ip = local->realip;
      }
      if (ip && (client->proxyip != ip || client->proxyport != ports.num)) {
        char ipstr[100];
        LOG_CODE_INFO_ (strcpy (ipstr, network_convert_ip (ip)));
        LOG_INFO_ ("proxy $%d %s:%d -> %s:%lld '%s'\n", client->sock->fd, network_convert_ip (client->proxyip),
                   client->proxyport, ipstr, ports.num, client->contact->nick.str);
        client->proxyip = ip, client->proxyudport = client->proxyport = (int) ports.num;
        if (local) {
          client->param.ip[0] = ip;
          for (i = SIM_ARRAY_SIZE (client->param.ip); i--;)
            if ((client->param.port[i] && client->param.port[i] == client->param.port[1]) || i == 1)
              client->param.port[i] = (int) ports.num;
          nat_reverse_attempt (client);
        }
      }
    } else
      LOG_WARN_ ("invalid $%d proxy port %lld '%s'\n", client->sock->fd, ports.num, client->contact->nick.str);
  } else if (proxy_get_ip_proxy (NULL, &ip, &port) && ! (contact_list.test & 0x40000)) {
    err = client_send_cmd (client, SIM_CMD_REPLY_PROXY, SIM_CMD_REPLY_PROXY_IP, string_copy (network_convert_ip (ip)),
                           SIM_CMD_REPLY_PROXY_PORT, number_new (port));
  } else if (client->contact != contact_list.me)
    if (nat_get_connected (client) & CLIENT_FLAG_ACCEPTED && proxy_check_provided ())
      err = client_send_cmd (client, SIM_CMD_REPLY_PROXY, NULL, nil (), NULL, nil ());
  return err;
}

static int server_exec_reply_proxy (simclient client, const simtype table) {
  int port;
  unsigned ip;
  simtype ports = table_get_type_number (table, SIM_CMD_REPLY_PROXY_PORT);
  if (ports.typ != SIMNIL) {
    if (SIM_CHECK_SHORT (ports.num))
      client->proxyudport = (int) ports.num;
  } else if (nat_get_connected (client) == CLIENT_FLAG_CONNECTED && client->contact != contact_list.me)
    if (client->contact->auth >= CONTACT_AUTH_ACCEPTED && ! (contact_list.test & 0x80000))
      if (socket_get_peer (client->sock, &ip, &port) == SIM_OK)
        proxy_probe (client->contact->addr, ip, port, PROXY_PROBE_CONTACT);
  return SIM_OK;
}

static int server_exec_many_ (simclient client, const simtype table);

simtype server_proto_handshake, server_proto_client, server_proto_audio;
static simtype server_proto_table; /* keyed by command name (SIM_CMD_xxx), value is command index + 1 */

static const struct {
  simbool idle;
  const char *name;
  int (*handler) (simclient, const simtype);
} server_proto[] = {
  { 1, NULL, server_exec_invalid },
  { 1, SIM_CMD_BYE, server_exec_bye },
  { 1, SIM_CMD_PING, server_exec_ping },
  { 1, SIM_CMD_PONG, server_exec_pong_ },
  { 1, SIM_CMD_STATUS, server_exec_status },
  { 1, SIM_CMD_ACK, server_exec_ack },
  { 0, SIM_CMD_MSG, server_exec_msg },
  { 1, SIM_CMD_MANY, server_exec_many_ },
  { 0, SIM_CMD_XFER_INIT, server_exec_xfer_init },
  { 1, SIM_CMD_XFER_SEND, server_exec_xfer_send },
  { 1, SIM_CMD_XFER_RECV, server_exec_xfer_recv },
  { 1, SIM_CMD_XFER_DATA, server_exec_xfer_data_ },
  { 1, SIM_CMD_XFER_OFFSET, server_exec_xfer_offset },
  { 0, SIM_CMD_XFER_CLOSE, server_exec_xfer_close },
  { 1, SIM_CMD_XFER_END, server_exec_xfer_end },
  { 0, SIM_CMD_CALL, server_exec_call_ },
  { 0, SIM_CMD_RING, server_exec_ring },
  { 0, SIM_CMD_HANGUP, server_exec_hangup_ },
  { 0, SIM_CMD_REQUEST_CALL, server_exec_request_call_ },
  { 0, SIM_CMD_REPLY_CALL, server_exec_reply_call_ },
  { 1, SIM_CMD_UDP, server_exec_udp },
  { 1, SIM_CMD_REVERSE, server_exec_reverse },
  { 1, SIM_CMD_REVERSING, server_exec_reversing },
  { 1, SIM_CMD_REVERSEND, server_exec_reversend },
  { 1, SIM_CMD_REVERSED, server_exec_reversed_ },
  { 1, SIM_CMD_TRAVERSE_INIT, server_exec_traverse_init },
  { 1, SIM_CMD_TRAVERSE_START, server_exec_traverse_start },
  { 1, SIM_CMD_TRAVERSE_RESTART, server_exec_traverse_restart },
  { 1, SIM_CMD_TRAVERSE_INVERSE, server_exec_traverse_inverse },
  { 1, SIM_CMD_TRAVERSE_INVERSED, server_exec_traverse_inversed },
  { 1, SIM_CMD_TRAVERSE_REPLY, server_exec_traverse_reply },
  { 1, SIM_CMD_TRAVERSED, server_exec_traversed_ },
  { 1, SIM_CMD_DUPLICATE, server_exec_duplicate },
  { 1, SIM_CMD_REQUEST_VERIFY, server_exec_request_verify },
  { 1, SIM_CMD_REPLY_VERIFY, server_exec_reply_verify },
  { 1, SIM_CMD_REQUEST_PROXY, server_exec_request_proxy },
  { 1, SIM_CMD_REPLY_PROXY, server_exec_reply_proxy }
};

static int server_exec_many_ (simclient client, const simtype table) {
  int err = SIM_OK, err2 = SIM_OK, command;
  unsigned i;
  simtype array = table_get_array_table (table, SIM_CMD_MANY_MSG), oldcmd = nil ();
  for (i = 1; i <= array.len && err == SIM_OK; i++) {
    simtype subtable = array_detach (array, i), newcmd;
    if ((newcmd = table_detach_string (subtable, SIM_CMD)).typ != SIMNIL) {
      string_free (oldcmd);
    } else if ((newcmd = oldcmd).typ == SIMNIL) {
      table_add_number (subtable, SIM_CMD_MSG_ACK, table_get_number (table, SIM_CMD_MANY_ACK));
      newcmd = pointer_new (SIM_CMD_MSG);
    }
    if (! server_check_idle (oldcmd = newcmd))
      client->tick = system_get_tick ();
    LOG_XTRA_ ("many%d $%d %s\n", i, client->sock->fd, oldcmd.str);
    command = (int) table_get_key_number (server_proto_table, oldcmd);
    if ((err = server_proto[command].handler (client, subtable)) == SIM_LIMIT_NO_ERROR) {
      err2 = SIM_LIMIT_NO_ERROR;
      err = SIM_OK;
    }
    table_free (subtable);
  }
  string_free (oldcmd);
  return err == SIM_OK ? err2 : err;
}

void server_loop_ (simclient client) {
  int err = SIM_CLIENT_CANCELLED, fd = client->sock->fd, remeasure = param_get_number ("server.measure") * 1000;
  simcontact contact = client->contact;
  LOG_API_DEBUG_ ("$%d '%s'\n", fd, contact->nick.str);
  client->sock->flags |= SOCKET_FLAG_HANDSHAKE;
  if (simself.state == CLIENT_STATE_RUNNING && simself.status != SIM_STATUS_OFF) {
    int state, command = -2, measure = 0, init = 1;
    simnumber start;
    simtype table = nil (), event = table_new_name (2, SIM_EVENT_NET);
    table_add_pointer (event, SIM_EVENT_NET, SIM_EVENT_NET_CONNECT);
    table_add_number (event, SIM_EVENT_NET_CONNECT, SIM_OK);
    table_add_pointer (event, SIM_EVENT_NET_CONNECT_ENCRYPT, client->sock->crypt.sndcipher);
    table_add_pointer (event, SIM_EVENT_NET_CONNECT_DECRYPT, client->sock->crypt.rcvcipher);
    event_send (contact, event);
    client->sock->flags |= SOCKET_FLAG_CPU;
    server_set_ciphers (contact->client = client);
    if ((err = xfer_start_thread (client)) == SIM_OK)
      if ((err = client_send (client, client_new_status (client))) == SIM_OK)
        contact->flags &= ~CONTACT_FLAG_INFO;
    while (err == SIM_OK) {
      if ((err = msg_start_thread (client)) != SIM_OK) {
        command = -1;
        break;
      }
      if (command < -1)
        msg_get_ (client, true);
      while (err == SIM_OK) {
        client->flags &= ~CLIENT_FLAG_ERROR;
        command = 0;
        if (init == 1) {
          unsigned ip;
          measure = init = 0;
          if ((err = socket_get_peer (client->sock, &ip, NULL)) != SIM_OK)
            break;
          if (client->ownip != ip && ! sim_network_check_local (ip) && ! client_check_local (ip, false))
            measure = limit_get_param (LIMIT_PARAM_MEASURE);
          LOG_DEBUG_ ("speed $%d measure = %d\n", client->sock->fd, measure);
        }
        if (table.typ == SIMNIL && (err = client_recv_ (client, &table)) == SIM_SOCKET_RECV_TIMEOUT) {
          if (remeasure && measure && audio_status.client != client) {
            simnumber tick = system_get_tick ();
            if ((! client->sock->pinged || tick - client->sock->pinged >= remeasure) && ! proxy_check_required (true)) {
              simunsigned rcvd = SOCKET_GET_STAT_RECEIVED (client->sock), sent = SOCKET_GET_STAT_SENT (client->sock);
              limit_start_ping (client->sock, contact, tick, rcvd, sent, measure * 1000);
            }
          }
          if ((err = client_send_cmd_ (client, SIM_CMD_PING, NULL, nil (), NULL, nil ())) == SIM_OK) {
            type_free (table);
            if ((err = client_recv_ (client, &table)) == SIM_SOCKET_RECV_TIMEOUT)
              nat_traverse_reset (client, err = SIM_SERVER_TIMEOUT);
          }
          if (err != SIM_OK)
            type_free (table);
        } else if (err == SIM_OK) {
          if (table.typ != SIMARRAY_ARRAY) {
            simtype cmd = table_get_string (table, SIM_CMD);
            LOG_XTRA_ ("recv $%d %s\n", client->sock->fd, cmd.str);
            command = (int) table_get_key_number (server_proto_table, cmd.str ? cmd : pointer_new (SIM_CMD_MANY));
            if ((err = server_proto[command++].handler (client, table)) == SIM_LIMIT_NO_ERROR) {
              init = 1;
              err = SIM_OK;
            }
            table_free (table);
          } else
            err = server_exec_audio (client, table);
          table = nil ();
        }
      }
      msg_stop_thread_ (client);
      if (client->sock->err != SIM_OK) {
        if (client->sock->err == SIM_CLIENT_DROPPED || client->sock->err == SIM_SERVER_DROPPED)
          command = -1;
        break;
      }
      if (err != SIM_SERVER_RECONNECT) {
        if (command > 0 || nat_reverse_succeed_ (client, true) != SIM_OK)
          break;
        LOG_NOTE_ ("recv $%d:$%d error %d\n", fd, client->sock->fd, err);
        command = -1;
        err = client_send_ (client, client_new_status (client));
        init++;
      } else {
        LOG_NOTE_ ("recv $%d error %d\n", client->sock->fd, err);
        err = SIM_OK;
      }
    }
    contact->client = NULL;
    limit_stop_ping (client->sock);
    socket_cancel (client->sock, err); /* fail send after this point */
    xfer_stop_thread_ (client);
    state = client->call.state, start = client->call.time;
    if (state != AUDIO_CALL_OUTGOING || client->flags & CLIENT_FLAG_RINGING || command < 0 ||
        msg_connect (contact, true) != SIM_OK) {
      event_test_error_audio (contact->id, audio_stop_ (client, err, true));
      if (state == AUDIO_CALL_TALKING) {
        if (audio_status.hangup)
          event_send_history_system (contact, "CALL ", audio_status.hangup, 0, start, err, true);
        audio_status.hangup = NULL;
      } else if (state != AUDIO_CALL_HANGUP)
        event_send_history_system (contact, "CALL ", "ABORT", 0, start, err, state == AUDIO_CALL_OUTGOING);
      /* if there is something to send, try to reconnect or set offline status, so that a reconnect
         will be initiated when contact is online again. otherwise, just try to set offline status */
      if (command >= 0) {
        if (contact->status != SIM_STATUS_OFF && xfer_check_pending (contact, client->xfer.cid)) {
          msg_connect (contact, false);
        } else if (client->flags & CLIENT_FLAG_ERROR && contact->status > SIM_STATUS_OFF) {
          if (! contact->dht.search)
            contact->dht.search = system_get_tick () + param_get_number ("main.research") * 1000;
          contact_probe (contact, 0, 1, NULL, CONTACT_PROBE_CONNECT);
        }
        if (SIM_CHECK_SHORT (client->proxyport) && client->proxyip && ! (contact_list.test & 0x100000))
          if (! contact_set_ip (contact, client->proxyip, client->proxyport, CONTACT_IP_UPDATE))
            contact_probe (contact, client->proxyip, client->proxyport, NULL, CONTACT_PROBE_DISCONNECT);
      }
    } else { /* an audio call was made but it didn't ring. initiate new connect attempt */
      MSG_SET_CALL (contact, start, client->call.sample);
      event_send_audio (contact, CONTACT_AUDIO_OUTGOING, CONTACT_AUDIO_CALLING);
    }
    event_send_name_number (contact, SIM_EVENT_NET, SIM_EVENT_NET_DISCONNECT, err == SIM_SERVER_EXIT ? SIM_OK : err);
  }
  if (contact->flags & CONTACT_FLAG_TYPE)
    contact_set_status (contact, contact->status, contact->flags & ~CONTACT_FLAG_TYPE);
  LOG_API_DEBUG_ ("$%d:$%d error %d '%s'\n", fd, client->sock->fd, err, contact->nick.str);
}

void server_logoff (int error, int oldstatus) {
  LOG_DEBUG_ ("status %s (old = %s)\n",
              error == SIM_SERVER_OFFLINE ? "OFF" : "off", CONTACT_LOOKUP_STATUS_NAME (oldstatus));
  simself.status = SIM_STATUS_OFF;
  if (oldstatus != SIM_STATUS_INVISIBLE)
    client_logoff (error == SIM_SERVER_OFFLINE ? SIM_STATUS_OFF : SIM_STATUS_INVISIBLE);
  client_cancel_probes (error == SIM_SERVER_OFFLINE ? SIM_STATUS_INVISIBLE : SIM_STATUS_OFF);
  client_cancel (NULL, error);
  proxy_cancel_proxy (NULL, error);
}

static void *thread_server_ (void *arg) {
  simnumber tick = system_get_tick (), ver;
  simcustomer customer = arg;
  simsocket sock = customer->sock;
  int err, fd = sock->fd, auth = CONTACT_AUTH_FORGET;
  simclient client = NULL;
  simcontact contact = NULL;
  unsigned ip, ownip = 0;
  LOG_API_DEBUG_ ("$%d\n", fd);
  err = proxy_handshake_server_ (customer, &ip);
  if (err == SIM_OK || (err == SIM_SERVER_CONTACT && param_get_number ("contact.strangers") == 1)) {
    if ((contact = contact_list_find_address (sock->master->from.ptr)) == NULL) {
      auth = param_get_number ("contact.strangers") >= 4 ? CONTACT_AUTH_ACCEPTED : CONTACT_AUTH_NEW;
      if (contact_list_check_request (tick)) {
        int err2 = contact_new_ (sock->master->from.ptr, auth, &contact);
        if (err2 == SIM_OK || err2 == SIM_CONTACT_EXISTS) {
          contact->seen[CONTACT_SEEN_RECEIVE] = time (NULL);
          LOG_DEBUG_ ("contact $%d %s '%s'\n", fd, err2 == SIM_OK ? "added" : "requested", contact->nick.str);
        } else {
          contact = NULL;
          err = err2;
          LOG_DEBUG_ ("contact $%d request error %d '%s'\n", fd, err, contact ? contact->nick.str : NULL);
        }
      }
    } else {
      LOG_DEBUG_ ("contact $%d connected (%lld ms) '%s'\n", fd, system_get_tick () - tick, contact->nick.str);
      contact->flags &= ~CONTACT_FLAG_NEW;
    }
    if (! contact || contact->auth < CONTACT_AUTH_NEW) {
      err = SIM_CONTACT_BLOCKED;
    } else
      err = contact_set_keys (contact, sock->master->pub[SSL_KEY_EC], sock->master->pub[SSL_KEY_RSA]);
  }
  if (err == SIM_OK) {
    simtype version, reply = table_new (9);
    crypt_open_socket (sock, 0, 0, 0, 0, true, false);
    sock->crypt.padding = param_get_number_min ("crypto.padding", param_get_number ("socket.padding"));
    if ((err = socket_recv_table_ (sock, server_proto_handshake, nil (), &version, NULL)) == SIM_OK) {
      if ((ver = table_get_number (version, SIM_REQUEST)) == SIM_PROTO_VERSION_REVERSE) {
#ifndef _WIN32
        if ((err = limit_test_client_ (sock, contact)) == SIM_OK)
#endif
          err = nat_reverse_accept_ (sock, contact, version, &client);
        if (err == SIM_OK) {
          proxy_customer_remove (customer);
          msg_recv_ack (contact, table_get_number (version, SIM_REQUEST_ACK));
        } else
          SIM_CALL_NULL (client_release, &client);
      } else if (ver >= SIM_PROTO_VERSION_MIN) {
        simbool oob = table_get_array_string (version, SIM_HANDSHAKE_TYPE).typ == SIMNIL;
        LOG_CODE_DEBUG_ (server_log_status (SIM_MODULE, SIM_LOG_DEBUG, sock, contact,
                                            ver, network_convert_ip (ip), version, tick, oob));
        if (contact->auth >= CONTACT_AUTH_ACCEPTED) {
          int flags = simself.flags & SIM_STATUS_FLAG_TCP_OUT ? SIM_REPLY_FLAG_VERIFY_OK : 0;
          int v = ver > SIM_PROTO_VERSION_MAX ? SIM_PROTO_VERSION_MAX : (int) ver, count, logon, factor, min;
          simunsigned nonce = table_get_number (version, SIM_REQUEST_RANDOM);
          simunsigned myage = contact == contact_list.me ? status_get_tick () : 0;
          simbool myself = contact == contact_list.me && nonce == simself.nonce[0];
          simbool info = (contact->flags & CONTACT_FLAG_INFO) != 0;
          nonce = random_get_number (random_public, 0xFFFFFFFF);
          if (contact_list.test & 0x20000)
            flags = SIM_REPLY_FLAG_VERIFY_NOK;
          if (myself) {
            nonce = simself.nonce[1];
            LOG_DEBUG_ ("connected $%d from #%lld self (#%lld)\n", fd, simself.nonce[0], nonce);
          } else if (! socket_check_server (sock)) {
            ownip = sim_network_parse_ip (table_get_pointer (version, SIM_REQUEST_TO));
            if (! sim_network_check_local (ownip)) {
              if (table_get_number (version, SIM_REQUEST_TO_PORT) == SIM_PROTO_PORT && ! sim_network_check_local (ip)) {
                simself.flags |= SIM_STATUS_FLAG_SSL_IN;
                LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
              }
              simself.flags |= SIM_STATUS_FLAG_IP;
              simself.ipin = ownip;
              LOG_DEBUG_ ("my ip $%d = %s '%s'\n", fd, network_convert_ip (ownip), contact->nick.str);
            } else if (! ownip)
              ownip = ip;
            table_add (reply, SIM_REPLY_FROM, string_copy (network_convert_ip (ip)));
          }
          server_add_versions (reply, contact, v, nonce, myage, oob, true);
          if (myself) {
            oob = true; /* do not enter client loop to avoid dropping myself */
          } else if (contact == contact_list.me)
            contact->seen[CONTACT_SEEN_RECEIVE] = time (NULL);
          if ((logon = min = param_get_number ("server.logon")) == 0) {
            for (factor = count = contact_list_count (true); factor >>= 1; logon++) {}
            factor = min = param_get_number ("client.logon");
            for (logon = count * (logon + 1) - (2 << logon) + 1; factor >>= 1; logon += count) {}
          }
          table_add_number (reply, SIM_REPLY_LOGON, logon < min ? min : logon);
          if (! oob) {
            err = limit_test_client_ (sock, contact);
            table_add (reply, SIM_REPLY_CODECS, audio_get_codecs ());
          } else if (info || xfer_check_pending (contact, table_get_number (version, SIM_REQUEST_ID)))
            flags |= SIM_REPLY_FLAG_MSG;
          if (contact != contact_list.me && ! socket_check_server (sock) && proxy_check_provided ())
            flags |= SIM_REPLY_FLAG_PROXY;
          if (client_find_connecting (contact, true))
            flags |= SIM_REPLY_FLAG_CONNECT;
          table_add (reply, SIM_REPLY_FLAGS, sim_convert_flags_to_strings (flags, SIM_ARRAY (server_flag_names)));
          server_add_ip (reply, sock, oob, true);
          table_add_number (reply, SIM_REPLY_ID, client_add_status (reply, contact));
          LOG_CODE_DEBUG_ (server_log_status (SIM_MODULE, SIM_LOG_DEBUG, sock, contact, 0, NULL, reply, 0, -1));
          if (err == SIM_OK) {
            if (! oob) {
              simclient other = client_cancel_contact (contact, SIM_SERVER_DROPPED);
              sock->flags |= SOCKET_FLAG_CLIENT;
              client = client_new (sock, contact, CLIENT_FLAG_ACCEPTED | CLIENT_FLAG_FORWARD);
              client->ownip = ownip;
              client->version = (int) ver;
              proxy_customer_remove (customer);
              if (other && other->proxyport)
                err = server_send_duplicate (client, other->sock, other->proxyip, other->proxyport);
            }
            if (err == SIM_OK)
              err = crypt_handshake_server_ (sock, reply, version);
          }
          if (err == SIM_OK) {
            if (oob) {
              sock->flags |= SOCKET_FLAG_HANDSHAKE;
              LOG_DEBUG_ ("disconnected $%d (%lld ms) '%s'\n", fd, system_get_tick () - tick, contact->nick.str);
            } else
              err = crypt_rsa_handshake_server_ (sock, contact, version);
          }
          if (err == SIM_OK && ! myself && socket_check_server (sock))
            proxy_set_score (contact, SIM_OK, PROXY_SCORE_OK);
          if (! oob && err == SIM_OK) {
            simtype ports = table_get_array_number (version, SIM_REQUEST_PORT);
            LOG_DEBUG_ ("connected $%d (%lld ms) '%s'\n", fd, system_get_tick () - tick, contact->nick.str);
            sock->rcvtimeout -= 2;
            if (sim_network_parse_ips_ports (table_get_array_string (version, SIM_REQUEST_IP), ports,
                                             SIM_ARRAY_SIZE (client->param.ip) - 1,
                                             client->param.ip + 1, client->param.port + 1) < 0)
              LOG_WARN_SIMTYPE_ (ports, 0, "invalid $%d global ports '%s' ", sock->fd, contact->nick.str);
            ports = table_get_array_number (version, SIM_REQUEST_LOCAL_PORT);
            if (sim_network_parse_ips_ports (table_get_array_string (version, SIM_REQUEST_LOCAL_IP), ports,
                                             SIM_ARRAY_SIZE (client->param.localip),
                                             client->param.localip, client->param.localport) < 0)
              LOG_WARN_SIMTYPE_ (ports, 0, "invalid $%d local ports '%s' ", sock->fd, contact->nick.str);
            table_delete (version, SIM_CMD_STATUS_STATUS);
            server_set_versions (client, contact, table_detach_array_string (version, SIM_REQUEST_BAD));
            err = server_set (contact, sock, client->param.ip[0] = ip, version, myage);
            if (auth == CONTACT_AUTH_ACCEPTED)
              event_send_value_number (contact, SIM_EVENT_CONTACT, SIM_EVENT_CONTACT, SIM_OK);
            auth = CONTACT_AUTH_DELETED;
            if (err == SIM_SERVER_OFFLINE) {
              server_logoff (SIM_SERVER_OFFLINE, SIM_STATUS_INVISIBLE);
            } else if (socket_check_server (sock)) {
              client->param.nonce[0] = table_get_number (version, SIM_REQUEST_RANDOM), client->param.nonce[1] = nonce;
              client_loop_ (client, true);
            } else
              client_loop_ (client, false);
          } else if (! myself) {
            server_set_versions (NULL, contact, table_detach_array_string (version, SIM_REQUEST_BAD));
            if (server_set (contact, sock, ip, version, myage) == SIM_SERVER_OFFLINE)
              server_logoff (SIM_SERVER_OFFLINE, SIM_STATUS_INVISIBLE);
          } else if (! socket_check_server (sock)) {
            SOCKET_INIT_STAT (sock);
            sock->created = 0;
          }
        } else {
          LOG_DEBUG_ ("handshake $%d disconnected '%s'\n", fd, contact->nick.str);
          server_set_versions (NULL, contact, table_detach_array_string (version, SIM_REQUEST_BAD));
          server_set (contact, sock, ip, version, 0);
          if (auth == CONTACT_AUTH_NEW)
            event_send_value_number (contact, SIM_EVENT_CONTACT, SIM_EVENT_CONTACT, SIM_OK);
          auth = CONTACT_AUTH_DELETED;
        }
      } else {
        LOG_NOTE_ ("handshake $%d failed (version %lld) '%s'\n", fd,
                   table_get_number (version, SIM_REQUEST), contact->nick.str);
        table_add_number (reply, SIM_REPLY, SIM_PROTO_VERSION_MIN);
        err = socket_send_table_ (sock, reply, SOCKET_SEND_TCP, NULL);
      }
    }
    table_free (version);
    table_free (reply);
    event_test_error_crypto (contact, contact->addr, err);
  }
  if (err == SIM_PROXY_LOCAL) {
    err = SIM_OK;
  } else if (err != SIM_OK)
    LOG_DEBUG_ ("handshake $%d error %d (%lld ms) '%s'\n", fd, err,
                system_get_tick () - tick, contact ? contact->nick.str : NULL);
  if (contact && auth >= CONTACT_AUTH_NEW) {
    if (auth == CONTACT_AUTH_NEW) {
      server_set_ip (contact, sock, ip, 0, 0, 0);
      if (err != SIM_SERVER_CONTACT || param_get_number ("contact.strangers") > 1)
        LOG_WARN_ ("requested (error %d) '%s'\n", err, contact->nick.str); /* new contact failed to make the handshake */
    }
    event_send_value_number (contact, SIM_EVENT_CONTACT, SIM_EVENT_CONTACT, SIM_OK);
  }
  if (! client) {
    if (contact)
      socket_close (sock, contact);
    proxy_customer_release (customer, SIM_CLIENT_NOT_FOUND);
  } else
    client_release (client);
  LOG_API_DEBUG_ ("$%d error %d\n", fd, err);
  return pth_thread_exit_ (true);
}

static void *thread_tcp_ (void *arg) {
  int err = 0, fd, fd2 = -1, port;
  simsocket sock = arg, server = NULL;
  LOG_API_DEBUG_ ("\n");
  while (socket_udp_sock.fd != INVALID_SOCKET) {
    err = socket_select_readable_ (fd = sock->fd, 1, 0);
    if (sock->fd != fd)
      continue;
    if (err > 0) {
      simcustomer customer = NULL;
      if ((err = limit_test_socket_ (NULL)) == SIM_OK) {
        customer = proxy_customer_new (PROXY_TYPE_NEW);
        err = socket_accept (sock, server = customer->sock, &customer->ip, &port);
        fd2 = server->fd;
      }
      if (err != SIM_OK || simself.state == CLIENT_STATE_STOPPED) {
        if (err != SIM_SOCKET_ACCEPT)
          pth_sleep_ (1); /* out of file descriptors or received TCP connection while logging off */
        proxy_customer_release (customer, err == SIM_OK ? SIM_CLIENT_CANCELLED : err);
      } else if ((err = limit_check_blacklisted (customer, customer->ip)) != SIM_OK) {
        proxy_customer_release (customer, err);
      } else if ((err = network_get_ip (), pth_thread_spawn (thread_server_, customer, NULL, NULL, fd2)) != SIM_OK) {
        proxy_customer_release (customer, err);
      } else if (! sim_network_check_local (customer->ip) && ! client_check_local (customer->ip, true)) {
        simself.flags |= SIM_STATUS_FLAG_TCP_IN;
        if (simself.oldflags & SIM_STATUS_FLAG_SSL_IN) {
          simself.flags |= SIM_STATUS_FLAG_SSL_IN;
        } else if (socket_get_addr (server, NULL, &port) == SIM_OK && port == SIM_PROTO_PORT)
          simself.flags |= SIM_STATUS_FLAG_SSL_IN;
        simself.oldflags = 0;
        simself.tickin = server->created;
        LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
        if (! proxy_check_required (true))
          client_cancel_proxy (SIM_PROXY_NOT_NEEDED);
      }
    } else if (err && socket_udp_sock.fd != INVALID_SOCKET)
#ifndef _WIN32
      if (errno != EINTR)
#endif
      {
        LOG_ERROR_ ("select $%d error %d\n", fd, socket_get_errno ());
        pth_sleep_ (1);
      }
  }
  LOG_API_DEBUG_ ("error %d\n", err);
  return pth_thread_exit_ (false);
}

int server_ping (simclient client) {
  if (client) {
    if (audio_status.client != client)
      return SIM_CALL_HANGUP;
    server_audio_ping_tick = system_get_tick ();
  } else
    server_audio_pong_tick = server_audio_ping_tick = 0;
  return SIM_OK;
}

int server_send_packet_ (simclient client, simtype arrays, int sequence) {
  simnumber tick = server_audio_ping_tick;
#ifdef DONOT_DEFINE
  simtype *array = NULL;
  if (arrays.len != SIM_CMD_AUDIO_NUMBERS || ! arrays.arr[SIM_CMD_AUDIO_DATA].len ||
      array_size (*(array = &arrays.arr[SIM_CMD_AUDIO_NUMBERS])) < SIM_CMD_AUDIO_NUMBERS_LATENCY) {
    LOG_FATAL_ (SIM_OK, "array size = %u[%u,%u] (wanted %u[%u,%u])\n", arrays.len, arrays.len ? arrays.arr[1].len : 0,
                arrays.len == SIM_CMD_AUDIO_NUMBERS ? array_size (*array) : 0,
                SIM_CMD_AUDIO_NUMBERS, 1, SIM_CMD_AUDIO_NUMBERS_LATENCY);
    audio_free_packet (arrays);
    return SIM_OK;
  }
#else
  simtype *array = &arrays.arr[SIM_CMD_AUDIO_NUMBERS];
#endif
  if (! tick && ! server_audio_pong_tick && ! arrays.arr[SIM_CMD_AUDIO_DATA].arr[1].len) {
    audio_free_packet (arrays);
    return SIM_OK;
  }
  if (tick) {
#ifndef SIM_DEBUG_LATENCY
    tick = system_get_tick ();
#endif
    if (tick - client->sock->created >= 0)
      array->arr[array->len = SIM_CMD_AUDIO_NUMBERS_PING] = number_new (tick - client->sock->created + 1);
    server_audio_ping_tick = 0;
  } else
    array->arr[SIM_CMD_AUDIO_NUMBERS_PING] = number_new (0);
  if (server_audio_pong_tick) {
    simnumber latency = proxy_get_latency (client, CLIENT_PONG_PROXY);
#ifndef SIM_DEBUG_LATENCY
    tick = system_get_tick () - server_audio_pong_tick + server_audio_pong_value;
#else
    tick = server_audio_pong_value;
#endif
    array->arr[array->len = SIM_CMD_AUDIO_NUMBERS_PONG] = number_new (tick);
    if (latency) {
      array->arr[array->len = SIM_CMD_AUDIO_NUMBERS_PROXY] = number_new (latency);
    } else
      array->arr[SIM_CMD_AUDIO_NUMBERS_PROXY] = number_new (0);
    if ((latency = client->latency[CLIENT_PONG_CLIENT]) != 0)
      array->arr[array->len = SIM_CMD_AUDIO_NUMBERS_LATENCY] = number_new (latency);
    server_audio_pong_tick = 0;
  }
  if (array->len == SIM_CMD_AUDIO_NUMBERS_TIME) {
    tick = array->arr[SIM_CMD_AUDIO_NUMBERS_TIME].num;
    array_free (*array);
    arrays.arr[SIM_CMD_AUDIO_NUMBERS] = number_new (tick);
  }
  if (client->sock->udport) {
    simnumber seq = client->flags & CLIENT_FLAG_ACCEPTED ? -sequence : ((simnumber) 1 << 62) - sequence;
    return client_send_udp (client, arrays, seq, client->sock->ip, client->sock->udport);
  }
  return client_send_ (client, arrays);
}

int server_recv_udp (simclient client, const simtype input, simnumber sequence, unsigned ip, int port) {
  int err = SIM_SOCKET_BAD_PACKET;
#if HAVE_LIBSPEEX
  const int b = SOCKET_RECV_UDP;
  simsocket sock = client->sock;
  simtype table = nil ();
  if (client->call.state == AUDIO_CALL_TALKING || client->call.state == AUDIO_CALL_OUTGOING)
    err = socket_recv_table (sock, server_proto_client, server_proto_audio, sequence, input, b, &table);
  if (err != SIM_OK) {
    if (sequence == -1 || sequence == ((simnumber) 1 << 62) - 1) {
      int rate = 0;
      if (client != audio_status.client && (rate = audio_get_param (AUDIO_PARAM_RATE)) != 0)
        err = socket_recv_table (sock, server_proto_client, server_proto_audio, sequence + 1 - rate, input, b, &table);
      if (err != SIM_OK && (rate = audio_get_param (AUDIO_PARAM_OLDRATE)) != 0)
        err = socket_recv_table (sock, server_proto_client, server_proto_audio, sequence + 1 - rate, input, b, &table);
      if (err == SIM_OK) {
        LOG_XTRA_SIMTYPE_ (table, LOG_BIT_BIN, "udp $%d bad packet %dHz (%u bytes) '%s' ", sock->fd,
                           rate, input.len, client->contact->nick.str);
        audio_free_packet (table);
        sequence = 0;
      }
    }
    event_test_error_crypto (client->contact, client->contact->addr, err);
  } else if (sock->err == SIM_OK) {
    if (sock->ip && sock->ip != ip && client->flags & CLIENT_FLAG_UDP) {
      char ipstr[100];
      LOG_CODE_WARN_ (strcpy (ipstr, network_convert_ip (ip)));
      LOG_WARN_SIMTYPE_ (table, table.typ == SIMARRAY_ARRAY ? LOG_BIT_BIN : 0,
                         "udp $%d bad ip (%u bytes) from %s:%d (wanted %s:%d) ", sock->fd,
                         type_size (table), ipstr, port, network_convert_ip (sock->ip), sock->udport);
      audio_free_packet (table);
    } else if (table.typ != SIMARRAY_ARRAY) {
      simtype udp = table_get_string (table, SIM_CMD_UDP);
      simbool ack = ! string_check_diff (udp, SIM_CMD_UDP_ACK);
      if (ack || ! string_check_diff (udp, SIM_CMD_UDP_CALL)) {
        simnumber srcport = table_get_number (table, SIM_CMD_UDP_SRC_PORT);
        if (SIM_CHECK_SHORT (srcport)) {
          if (! sock->ip)
            LOG_DEBUG_ ("udp $%d ip %s:%lld\n", sock->fd, network_convert_ip (ip), srcport);
          sock->udport = port = (int) srcport;
          sock->ip = ip;
          server_set_qos (sock, 0);
          audio_reset (client);
        }
      }
      LOG_DEBUG_SIMTYPE_ (table, 0, "udp $%d recv (%u bytes) from %s:%d '%s' ", sock->fd, input.len,
                          network_convert_ip (ip), port, client->contact->nick.str);
      if (ack || ! string_check_diff (udp, SIM_CMD_UDP_REQUEST)) {
        if ((udp = table_get_string (table, SIM_CMD_UDP_DST_IP)).typ != SIMNIL)
          table_set (table, SIM_CMD_UDP_SRC_IP, string_copy_string (udp));
        table_set_number (table, SIM_CMD_UDP_SRC_PORT, table_get_number (table, SIM_CMD_UDP_DST_PORT));
        audio_send_udp (client, table, ack ? SIM_CMD_UDP_CALL : SIM_CMD_UDP_ACK, ip, port);
        table = nil ();
      }
      table_free (table);
    } else {
      if (sock->udport && ! (client->flags & CLIENT_FLAG_UDP) && client->call.state == AUDIO_CALL_TALKING) {
        client->flags |= CLIENT_FLAG_UDP;
        event_send_audio (client->contact, CONTACT_AUDIO_TALKING, CONTACT_AUDIO_UDP);
      }
      server_exec_audio (client, table);
    }
  } else
    audio_free_packet (table);
  if (err == SIM_OK) {
    unsigned len = input.len;
    if (sequence != -1 && sequence != ((simnumber) 1 << 62) - 1) {
      simsocket local = SOCKET_GET_STAT_SOCKET (sock);
      local->rcvbytes += len, local->rcvheaders += SOCKET_CALC_OVERHEAD_UDP (len);
    } else
      client->contact->stats[CONTACT_STATS_OUTPUT][CONTACT_STAT_RECEIVED] += len + SOCKET_CALC_OVERHEAD_UDP (len);
  }
#endif
  return err;
}

static void *thread_udp_ (void *arg) {
  time_t tosleep = 0;
  LOG_API_DEBUG_ ("$%d\n", socket_udp_sock.fd);
  while (socket_udp_sock.fd != INVALID_SOCKET) {
    int len = 0, err, fd = socket_udp_sock.fd;
    time_t t = tosleep;
    const char *fun = "recv";
    while (t--)
      if (socket_udp_sock.fd == INVALID_SOCKET || (len = socket_select_readable_ (fd = socket_udp_sock.fd, 1, 0)) != 0)
        break;
    if (! len && socket_udp_sock.fd != INVALID_SOCKET)
      len = socket_select_readable_ (fd = socket_udp_sock.fd, 0, sim_get_random (999) + 1);
    if (socket_udp_sock.fd != fd)
      continue;
    if (len > 0 && fd != INVALID_SOCKET) {
      static simbyte server_udp_buffer[16385];
      struct sockaddr_in sin;
      socklen_t sinlen = sizeof (sin);
      unsigned ip = 0;
      const unsigned size = sizeof (server_udp_buffer) - 1;
      if ((len = recvfrom (fd, (char *) server_udp_buffer, size, 0, (struct sockaddr *) &sin, &sinlen)) > 0)
        if (! sim_network_check_local (ip = ntohl (sin.sin_addr.s_addr))) {
          simself.flags |= SIM_STATUS_FLAG_UDP_OUT;
          LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
        }
      if (len > 2 && ! SOCKET_CHECK_MAINLINE (server_udp_buffer)) {
        simtype buf = pointer_new_len (server_udp_buffer, len);
        if (param_get_number ("net.tor.port") > 0) {
          err = SIM_SOCKET_BAD_PACKET;
        } else if ((err = client_recv_udp (buf, ip, ntohs (sin.sin_port))) != SIM_OK)
          LOG_XTRA_SIMTYPE_ (buf, LOG_BIT_BIN, "udp bad packet (%u bytes) at from %s:%d (error %d) ",
                             len, inet_ntoa (sin.sin_addr), ntohs (sin.sin_port), err);
        if (err != SIM_OK && err != SIM_SOCKET_NO_ERROR) {
          len += SOCKET_CALC_OVERHEAD_UDP ((unsigned) len);
          contact_list.me->stats[CONTACT_STATS_CLIENT][CONTACT_STAT_RECEIVED] += len;
        }
        len = 0;
      }
      if (len >= 0) {
        server_udp_buffer[len] = 0;
        main_recv_udp (&tosleep, server_udp_buffer, len, &sin, (int) sinlen);
      }
    } else if (len) {
      err = socket_get_errno ();
#ifndef _WIN32
      if (err != EINTR)
#endif
        pth_sleep_ (1);
      socket_set_errno (err);
      fun = "select";
    } else
      main_recv_udp (&tosleep, NULL, 0, NULL, 0);
    if (len < 0) {
      err = socket_get_errno ();
#ifdef _WIN32
      if (err != WSAEINTR && err != WSAEWOULDBLOCK) {
#else
      if (err != EINTR && err != EAGAIN && err != EWOULDBLOCK) {
#endif
#ifdef _WIN32
        if (err == WSAECONNRESET || err == WSAEMSGSIZE || err == WSAENETRESET)
          continue; /* ICMP error / UDP packet too large / UDP timeout */
#endif
        LOG_ERROR_ ("%s error %d\n", fun, err);
        tosleep = 1;
      } else
        tosleep = 0;
      main_recv_udp (&tosleep, NULL, -1, NULL, 0);
    }
  }
  LOG_API_DEBUG_ ("\n");
  return pth_thread_exit_ (false);
}

static int server_listen_udp (int port, int *fd, int *newport) {
  int err;
  unsigned ip, mainip = sim_network_parse_ip_default (param_get_pointer ("main.ip"), &ip);
  LOG_INFO_ ("default interface %s\n", network_convert_ip (mainip));
  if ((err = socket_listen_udp (ip ? ip : mainip, port, fd, newport)) != SIM_OK) {
    LOG_WARN_ ("network interface unknown %s\n", network_convert_ip (ip ? ip : mainip));
    err = socket_listen_udp (sim_network_parse_ip_default (NULL, &ip), port, fd, newport);
  }
  return err;
}

int server_set_port (int port) {
  struct _socket sock;
  int fd, oldval = abs (param_get_number ("main.port")), oldport, err = SIM_OK;
  LOG_DEBUG_ ("set port %d\n", port);
  if (port && port != oldval && (err = server_listen_udp (port, &fd, NULL)) == SIM_OK) {
    if (socket_udp_sock.fd != INVALID_SOCKET && server_tcp_sock.fd != INVALID_SOCKET) {
      if ((err = main_set_port (fd, port)) == SIM_OK)
        if (port != SIM_PROTO_PORT || server_https_sock.fd == INVALID_SOCKET)
          if ((err = socket_get_addr (&server_tcp_sock, NULL, &oldport)) == SIM_OK && port != oldport) {
            if ((err = socket_listen_tcp (&sock, 0, port, NULL)) == SIM_OK) {
              simtype host = param_get_string ("client.host");
              simself.flags &= ~(SIM_STATUS_FLAG_DHT_IN | SIM_STATUS_FLAG_UDP_IN |
                                 SIM_STATUS_FLAG_TCP_IN | SIM_STATUS_FLAG_UPNP);
              LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
              socket_close (&server_tcp_sock, NULL);
              memcpy (&server_tcp_sock, &sock, sizeof (sock));
              if (host.len && host.str[0] != '.')
                contact_list_set_info (0);
              client_cancel (NULL, SIM_SOCKET_BAD_PORT);
              network_set_port (port);
            } else if (server_listen_udp (oldval, &fd, &oldval) == SIM_OK) {
              main_set_port (fd, oldval); /* should be impossible to fail */
            } else
              LOG_WARN_ ("port %d is not available any more\n", oldval);
          }
    } else {
      close_socket (fd);
      if ((err = socket_listen_tcp (&sock, 0, port, NULL)) == SIM_OK)
        socket_close (&sock, NULL);
    }
  }
  return err;
}

int server_get_port (void) {
  int port = SIM_PROTO_PORT;
  if (server_https_sock.fd == INVALID_SOCKET) {
    port = 0;
    if (server_tcp_sock.fd != INVALID_SOCKET)
      socket_get_addr (&server_tcp_sock, NULL, &port);
  }
  return port;
}

simbool server_check_idle (const simtype cmd) {
  int command = 0;
  if (cmd.typ != SIMNIL && server_proto_table.typ != SIMNIL)
    command = (int) table_get_key_number (server_proto_table, cmd);
  return server_proto[command].idle;
}

static void server_init_proto (void) {
  unsigned i, versions = param_get_number ("server.versions");
  simtype tmp;
  server_proto_table = table_new_const (199, NULL);
  for (i = 1; i < SIM_ARRAY_SIZE (server_proto); i++)
    table_add_number (server_proto_table, server_proto[i].name, i);
  server_proto_handshake =
    table_new_const (597, SIM_NEW_STRING (SIM_CMD), SIM_NEW_STRING (SIM_CMD_UDP), SIM_NEW_NUMBER (SIM_CMD_UDP_TIME),
                     SIM_NEW_STRING (SIM_CMD_UDP_SRC_IP), SIM_NEW_NUMBER (SIM_CMD_UDP_SRC_PORT),
                     SIM_NEW_STRING (SIM_CMD_UDP_DST_IP), SIM_NEW_NUMBER (SIM_CMD_UDP_DST_PORT),
                     SIM_NEW_NUMBER (SIM_CMD_PING_PONG), SIM_NEW_NUMBER (SIM_CMD_PING_PONG_NUMBER),
                     SIM_NEW_STRING (SIM_CMD_PING_PAD), SIM_NEW_STRING (SIM_CMD_PONG_PAD),
                     SIM_NEW_NUMBER (SIM_CMD_PONG_AUDIO_READ), SIM_NEW_NUMBER (SIM_CMD_PONG_AUDIO_WRITE),
                     SIM_NEW_NUMBER (SIM_CMD_PONG_AUDIO_ALL), SIM_NEW_NUMBER (SIM_CMD_PONG_AUDIO_LOST),
                     SIM_NEW_STRING (SIM_CMD_STATUS_STATUS), SIM_NEW_STRINGS (SIM_CMD_STATUS_RIGHTS, 15),
                     SIM_NEW_NUMBER (SIM_CMD_STATUS_EDIT), SIM_NEW_STRING (SIM_CMD_STATUS_NICK),
                     SIM_NEW_STRING (SIM_CMD_STATUS_LINE), SIM_NEW_STRINGS (SIM_CMD_STATUS_HOSTS, 2),
                     SIM_NEW_STRING (SIM_CMD_MSG_TYPE), SIM_NEW_STRING (SIM_CMD_MSG_TEXT),
                     SIM_NEW_NUMBER (SIM_CMD_MSG_HANDLE), SIM_NEW_NUMBER (SIM_CMD_MSG_EDIT),
                     SIM_NEW_NUMBER (SIM_CMD_MSG_TIME), SIM_NEW_NUMBER (SIM_CMD_MSG_ACK),
                     SIM_NEW_NUMBER (SIM_CMD_ACK_HANDLE), SIM_NEW_NUMBER (SIM_CMD_MANY_ACK),
                     SIM_NEW_STRING (SIM_CMD_XFER_DATA_BYTES), SIM_NEW_NUMBER (SIM_CMD_XFER_INIT_PAUSE),
                     SIM_NEW_NUMBER (SIM_CMD_XFER_INIT_NAME), SIM_NEW_NUMBER (SIM_CMD_XFER_INIT_NAMES),
                     SIM_NEW_STRING (SIM_CMD_XFER_SEND_TYPE), SIM_NEW_STRING (SIM_CMD_XFER_SEND_NAME),
                     SIM_NEW_NUMBER (SIM_CMD_XFER_SEND_HANDLE), SIM_NEW_NUMBER (SIM_CMD_XFER_SEND_SIZE),
                     SIM_NEW_NUMBER (SIM_CMD_XFER_SEND_TIME), SIM_NEW_NUMBER (SIM_CMD_XFER_RECV_HANDLE),
                     SIM_NEW_STRING (SIM_CMD_XFER_RECV_HASH), SIM_NEW_NUMBER (SIM_CMD_XFER_RECV_SIZE),
                     SIM_NEW_NUMBER (SIM_CMD_XFER_OFFSET_SIZE), SIM_NEW_NUMBER (SIM_CMD_XFER_OFFSET_SPEED),
                     SIM_NEW_NUMBER (SIM_CMD_XFER_CLOSE_HANDLE), SIM_NEW_STRING (SIM_CMD_XFER_CLOSE_HASH),
                     SIM_NEW_NUMBER (SIM_CMD_XFER_CLOSE_TIME), SIM_NEW_NUMBER (SIM_CMD_XFER_CLOSE_ERROR),
                     SIM_NEW_NUMBER (SIM_CMD_XFER_END_HANDLE), SIM_NEW_NUMBER (SIM_CMD_XFER_END_ERROR),
                     SIM_NEW_STRING (SIM_CMD_CALL_CODEC), SIM_NEW_NUMBER (SIM_CMD_CALL_SAMPLE),
                     SIM_NEW_NUMBER (SIM_CMD_CALL_QUALITY), SIM_NEW_NUMBER (SIM_CMD_CALL_FRAMES),
                     SIM_NEW_NUMBER (SIM_CMD_CALL_MTU), SIM_NEW_NUMBER (SIM_CMD_CALL_ECHO),
                     SIM_NEW_NUMBER (SIM_CMD_HANGUP_ERROR), SIM_NEW_NUMBER (SIM_CMD_RING_LATENCY),
                     SIM_NEW_NUMBER (SIM_CMD_RING_LATENCY_ADD), SIM_NEW_NUMBER (SIM_CMD_RING_SAMPLE),
                     SIM_NEW_NUMBER (SIM_CMD_RING_QUALITY), SIM_NEW_NUMBER (SIM_CMD_RING_FRAMES),
                     SIM_NEW_NUMBER (SIM_CMD_REQUEST_CALL_SAMPLE), SIM_NEW_NUMBER (SIM_CMD_REPLY_CALL_SAMPLE),
                     SIM_NEW_NUMBER (SIM_CMD_TRAVERSE_TIME), SIM_NEW_NUMBER (SIM_CMD_TRAVERSE_DELAY),
                     SIM_NEW_NUMBER (SIM_CMD_TRAVERSE_PORT), SIM_NEW_NUMBER (SIM_CMD_TRAVERSE_DELTA),
                     SIM_NEW_NUMBER (SIM_CMD_TRAVERSE_RANDOM), SIM_NEW_STRING (SIM_CMD_DUPLICATE_IP),
                     SIM_NEW_NUMBER (SIM_CMD_DUPLICATE_PORT), SIM_NEW_STRING (SIM_CMD_REQUEST_VERIFY_IP),
                     SIM_NEW_NUMBER (SIM_CMD_REQUEST_VERIFY_PORT), SIM_NEW_STRINGS (SIM_CMD_REPLY_VERIFY_FLAGS, 1),
                     SIM_NEW_STRING (SIM_CMD_REQUEST_PROXY_IP), SIM_NEW_NUMBER (SIM_CMD_REQUEST_PROXY_PORT),
                     SIM_NEW_STRING (SIM_CMD_REPLY_PROXY_IP), SIM_NEW_NUMBER (SIM_CMD_REPLY_PROXY_PORT),
                     SIM_NEW_STRING (SIM_SHAKEHAND_TYPE), SIM_NEW_NUMBER (SIM_SHAKEHAND_TAG_SIZE),
                     SIM_NEW_STRING (SIM_SHAKEHAND_ENCRYPT), SIM_NEW_STRING (SIM_SHAKEHAND_DECRYPT),
                     SIM_NEW_NUMBER (SIM_REPLY), SIM_NEW_NUMBER (SIM_REPLY_AGE), SIM_NEW_NUMBER (SIM_REPLY_RANDOM),
                     SIM_NEW_STRING (SIM_REPLY_FROM), SIM_NEW_STRINGS (SIM_REPLY_IP, 4),
                     SIM_NEW_NUMBERS (SIM_REPLY_PORT, 4), SIM_NEW_STRINGS (SIM_REPLY_LOCAL_IP, 1),
                     SIM_NEW_NUMBERS (SIM_REPLY_LOCAL_PORT, 1), SIM_NEW_NUMBER (SIM_REPLY_ACK),
                     SIM_NEW_STRING (SIM_REPLY_CODECS), SIM_NEW_NUMBER (SIM_REPLY_LOGON),
                     SIM_NEW_STRINGS (SIM_REPLY_FLAGS, 8), SIM_NEW_NUMBER (SIM_REPLY_SEQUENCE),
                     SIM_NEW_STRINGS (SIM_HANDSHAKE_TYPE, 5), SIM_NEW_NUMBER (SIM_HANDSHAKE_TAG_SIZE),
                     SIM_NEW_NUMBERS (SIM_HANDSHAKE_PREFERRED, 11), SIM_NEW_NUMBERS (SIM_HANDSHAKE_SUPPORTED, 11),
                     SIM_NEW_NUMBER (SIM_REQUEST), SIM_NEW_NUMBER (SIM_REQUEST_SEQUENCE),
                     SIM_NEW_NUMBER (SIM_REQUEST_AGE), SIM_NEW_NUMBER (SIM_REQUEST_RANDOM),
                     SIM_NEW_STRING (SIM_REQUEST_TO), SIM_NEW_NUMBER (SIM_REQUEST_TO_PORT),
                     SIM_NEW_STRING (SIM_REQUEST_PROXY_IP), SIM_NEW_NUMBER (SIM_REQUEST_PROXY_PORT),
                     SIM_NEW_STRINGS (SIM_REQUEST_IP, 4), SIM_NEW_NUMBERS (SIM_REQUEST_PORT, 4),
                     SIM_NEW_STRINGS (SIM_REQUEST_LOCAL_IP, 1), SIM_NEW_NUMBERS (SIM_REQUEST_LOCAL_PORT, 1),
                     SIM_NEW_NUMBER (SIM_REQUEST_ACK), SIM_NEW_STRING (SIM_REQUEST_CODECS),
                     SIM_NEW_STRINGS (SIM_REQUEST_FLAGS, 8), SIM_NEW_STRING (SIM_REQUEST_REVOKE),
                     SIM_NEW_NUMBER (SIM_REQUEST_ID), SIM_NEW_NUMBER (SIM_REPLY_ID), NULL);
  table_add (server_proto_handshake, SIM_REQUEST_BAD, array_new_types (versions, pointer_new ("")));
  table_add (server_proto_handshake, SIM_REPLY_BAD, array_new_types (versions, pointer_new ("")));
  tmp = array_new_type (table_copy (server_proto_handshake, server_proto_handshake.len));
  tmp.len = SIM_MAX_PACKET_SIZE;
  table_add (server_proto_handshake, SIM_CMD_MANY_MSG, tmp);
  server_proto_client = server_proto_handshake;
  server_proto_audio = array_new_types (SIM_CMD_AUDIO_NUMBERS, array_new_type (pointer_new ("")));
  server_proto_audio.arr[SIM_CMD_AUDIO_NUMBERS] = array_new_types (SIM_CMD_AUDIO_NUMBERS_LATENCY, number_new (0));
}

int server_init_ (simbool init) {
  int err = SIM_OK, err2, fd, dhtport, tcpport = 0, retry = 0, port = abs (param_get_number ("main.port"));
  simtype dht;
  LOG_DEBUG_ ("init port %d\n", port);
  if (socket_udp_sock.fd != INVALID_SOCKET || tid_udp)
    return SIM_SERVER_INIT;
  simself.oldflags = simself.flags = 0;
  LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
  if (init && (err = main_dht_load (&dht)) == SIM_OK) {
    random_init_entropy (dht);
    table_free (dht);
    if ((err = random_init (random_session)) == SIM_OK) {
      contact_list_init ();
      if ((err = key_save_ (KEY_MODE_LOAD)) == SIM_OK) {
        while (! table_init_hash ((unsigned) random_get_number (random_session, 0xFFFFFFFF))) {}
        limit_init ();
      }
    }
    if (err != SIM_OK)
      random_init (random_private);
  }
  socket_new (&server_tcp_sock);
  socket_new (&server_https_sock);
  while (err == SIM_OK) {
    if ((err = server_listen_udp (port, &fd, &dhtport)) != SIM_OK)
      break;
    if (dhtport != port)
      LOG_DEBUG_ ("port = %d\n", dhtport);
    if (param_get_number ("net.tor.port") > 0) {
      unsigned ip = sim_network_parse_ip (param_get_pointer ("proxy.local"));
      simtype ipstr = param_get_default ("proxy.local", nil ());
      if ((err = ip ? socket_listen_tcp (&server_tcp_sock, ip, dhtport, &tcpport) : EADDRINUSE) != SIM_OK) {
        if ((ip = sim_network_parse_ip (ipstr.ptr)) == 0) {
          LOG_ERROR_ ("failed to bind to %s (error %d). proxy disabled\n", param_get_string ("proxy.local").str, err);
        } else if ((err = socket_listen_tcp (&server_tcp_sock, ip, dhtport, &tcpport)) != SIM_OK)
          LOG_ERROR_ ("failed to bind to %s (error %d). proxy disabled\n", param_get_string ("proxy.local").str, err);
      }
      if (err != SIM_OK) { /* cannot bind to localhost: don't open listening port but don't fail */
        tid_tcp = NULL;
        goto skip;
      }
    } else
      err = socket_listen_tcp (&server_tcp_sock, 0, dhtport, &tcpport);
    if (err == SIM_OK) {
      if (tcpport != dhtport) {
        port = 0;
        if (++retry > 65536)
          err = EADDRINUSE;
      } else {
        if (tcpport != port) {
          int oldport = param_get_number ("main.port");
          if (oldport > 0) {
            socket_close (&server_tcp_sock, NULL);
            close_socket (fd);
            sim_param_set_error (NULL, number_new (oldport), err = SIM_SERVER_PORT);
            goto done;
          }
          LOG_WARN_ ("changed port from %d to %d\n", -oldport, tcpport);
          if (param_set_number ("main.port", -tcpport, SIM_PARAM_UNAPPLIED) == SIM_OK) /* port is already open - don't reopen */
            if ((err = param_save ()) != SIM_OK)
              event_send_name_number (NULL, SIM_EVENT_ERROR, SIM_EVENT_ERROR_FILE_SAVE, err);
        }
        network_set_port (tcpport);
        tid_https = NULL;
        if (socket_listen_tcp (&server_https_sock, 0, SIM_PROTO_PORT, NULL) != SIM_OK ||
            (err = pth_thread_spawn (thread_tcp_, &server_https_sock, NULL, &tid_https, 0)) == SIM_OK) {
          if ((err = pth_thread_spawn (thread_tcp_, &server_tcp_sock, NULL, &tid_tcp, 1)) == SIM_OK) {
          skip:
            if ((err = pth_thread_spawn (thread_udp_, NULL, NULL, &tid_udp, -1)) == SIM_OK) {
              socket_set_udp (fd);
            done:
              if (! server_proto_table.ptr)
                server_init_proto ();
              proxy_init_stat (1);
              return err;
            }
            if ((err2 = pth_thread_join_ (&tid_tcp, NULL, thread_tcp_, 1)) != SIM_OK)
              err = err2;
          }
          if ((err2 = pth_thread_join_ (&tid_https, NULL, thread_tcp_, 0)) != SIM_OK)
            err = err2;
        }
        socket_close (&server_https_sock, NULL);
      }
      socket_close (&server_tcp_sock, NULL);
    }
    close_socket (fd);
  }
  return err;
}

int server_uninit_ (simbool init) {
  int err = SIM_OK, err2 = SIM_OK, err3 = SIM_SERVER_INIT, fd = socket_udp_sock.fd;
  LOG_DEBUG_ ("uninit\n");
  simself.status = SIM_STATUS_OFF;
  if (fd != INVALID_SOCKET) {
    socket_udp_sock.fd = INVALID_SOCKET;
    network_set_port (0);
    err = pth_thread_join_ (&tid_udp, NULL, thread_udp_, -1);
    err2 = pth_thread_join_ (&tid_tcp, NULL, thread_tcp_, 1);
    err3 = pth_thread_join_ (&tid_https, NULL, thread_tcp_, 0);
    close_socket (fd);
    socket_close (&server_tcp_sock, NULL);
    socket_close (&server_https_sock, NULL);
  }
  if (init && server_proto_table.ptr) {
    simtype tmp = table_detach_array_table (server_proto_handshake, SIM_CMD_MANY_MSG);
    tmp.len = 1;
    array_free (tmp);
    TABLE_FREE (&server_proto_handshake, number_new (0));
    ARRAY_FREE (&server_proto_audio, number_new (0));
    TABLE_FREE (&server_proto_table, nil ());
    server_proto_client = number_new (0);
  }
  proxy_init_stat (0);
  return err3 != SIM_OK ? err3 : err2 != SIM_OK ? err2 : err;
}

void server_log_status (const char *module, int level, simsocket sock, simcontact contact,
                        simnumber protover, const char *ipstr, const simtype table, simnumber tick, int oob) {
  simtype val = table_get_string (table, SIM_CMD_STATUS_STATUS);
  simtype array = table_get_array_string (table, SIM_CMD_STATUS_RIGHTS);
  if (oob >= 0) {
    log_simtype_ (module, level, val, LOG_BIT_CONT, "handshake $%d succeeded (version %lld) %s%s status ", sock->fd,
                  protover, oob ? "oob " : "", ipstr);
    log_any_ (module, level, " (%lld ms)", system_get_tick () - tick);
  } else
    log_simtype_ (module, level, val, LOG_BIT_CONT, "send $%d status ", sock->fd);
  log_simtype_ (module, level, array, 0, " '%s' flags ", contact->nick.str);
}
