/**
    tracking of internet connectivity and interface to mainline DHT

    Copyright (c) 2020-2022 The Creators of Simphone

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

#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* CLOCK_THREAD_CPUTIME_ID in time.h and RUSAGE_THREAD in resource.h */
#endif

#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 "file.h"
#include "socket.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 "client.h"
#include "api.h"

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

#ifndef _WIN32
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/resource.h>
#include <time.h>

#ifdef __APPLE__
#define MAIN_CPU_TIMER_RESOLUTION 1000
#else
#define MAIN_CPU_TIMER_RESOLUTION 4000000
#endif
#else /* _WIN32 */
#define MAIN_CPU_TIMER_RESOLUTION 15625000
#endif

#include "../dht/dht.h"

#define SIM_MODULE SIM_MODULE_MAIN

#define FILE_DHT "mainline"

#define MAIN_DHT_SIZE_ID 20 /* number of bytes in a DHT id */
#define MAIN_DHT_BUCKET 8   /* number of nodes in a K-bucket */

#define MAIN_DHT_CONNECTED_NODES 40

#define MAIN_CHECK_DISCONNECTED(good, dubious) \
  ((good) < 4 || (good) + (dubious) <= 8 || ((good) < MAIN_DHT_CONNECTED_NODES / 2 && 8 * (good) < (dubious)))
#define MAIN_CALC_DELAY(nodes, k, factor) (((nodes) / (k) + 1) * (factor))

static simunsigned main_cpu_tick; /* millisecond tick where main_cpu_recv was last reset */
static simunsigned main_cpu_load; /* maximal allowed DHT cpu load in nanoseconds per cpu burst */
static simunsigned main_cpu_recv; /* currently accumulated recv cpu load in nanoseconds */
static simunsigned main_cpu_send; /* currently accumulated send cpu load in nanoseconds */
static simtype main_cpu_table;    /* keyed by ip, value is number of cpu nanoseconds (odd = blacklisted) */
static unsigned main_cpu_count;   /* number of ip addresses in main_cpu_table */
static unsigned main_cpu_burst;   /* value of main.cpu.burst in milliseconds */
static int main_cpu_error;        /* error code of last sent limit event (normally SIM_LIMIT_DHT_ENABLE) */

static int main_blacklist_index, main_blacklist_size = 0; /* next element of main_blacklist_buffer */
static unsigned *main_blacklist_buffer = NULL;            /* cyclic buffer containing ip addresses */
static simtype main_blacklist_table;                      /* keyed by ip, value is high bytes of search id */
static int main_blacklist_nodes = 0;                      /* number of newly blacklisted nodes */

static int main_dht_state = -1;  /* UP = 1, DOWN = 0, INIT = -1 */
static int main_dht_status = -1; /* for SIM_EVENT_NET_DHT */
static int main_dht_running_flag = false, main_dht_port;
static unsigned main_dht_ip, main_dht_sender = 0;
static int main_dht_listening_value, main_dht_listening_threshold; /* threshold (incoming nodes) for listen state */
static int main_dht_listening_nodes = 0;
static pth_t tid_dht;

static simunsigned main_dht_announce_tick; /* time to self announce */
static int main_dht_mode, main_dht_size = 0;
static simbool main_dht_recursed_flag = false;
static simbyte main_dht_id[MAIN_DHT_SIZE_ID];
static simtype main_file; /* keyed by FILE_KEY_MAIN_xxx or contact_stat_names[CONTACT_STATS_CLIENTS], value depends on key type */

static simbool main_dht_warn_flag, main_dht_disconnected_flag; /* for main_get_nodes / main_dht_count */

static simbool main_send_error_flag, main_send_ok_flag, main_ssl_reinit_flag, main_dht_reinit_flag, main_down_flag;
static simunsigned main_down_tick = 0, main_dht_recv_tick, main_dht_verify_tick = 0;

static simnumber main_stat_tick = 0; /* session start tick or zero if no session */
static simnumber main_stats[2][4];   /* UDP statistics (indexed by CONTACT_STAT_xxx) */

static simtype main_search_table; /* keyed by salted search id, value is a table keyed by struct _network_ip_port */

static struct _contact main_proxy_contact; /* dummy */

#define MAIN_RESTART_ONCE 1        /* restart blacklisting search for the real one */
#define MAIN_RESTART_VERIFY 2      /* restart search until proxy announcement found */
#define MAIN_RESTART_ANNOUNCE 4    /* restart search until own announcement found */
#define MAIN_RESTART_BLACKLISTED 8 /* blacklisting search has found a malicious node */
#define MAIN_RESTART_SEARCH 16     /* not stored to struct main_search (only as an argument to main_search_create) */

struct main_search {
  int announce;                        /* port number to announce or zero for search */
  int map;                             /* xor announced port to real port number */
  int restart;                         /* bitmap of MAIN_RESTART_xxx */
  unsigned sequence;                   /* search number */
  int mode;                            /* MAIN_MODE_xxx */
  int alpha;                           /* number of parallel DHT searches to start */
  int beta;                            /* number of parallel DHT searches to follow */
  char addr[CONTACT_ADDRESS_SIZE + 1]; /* ASCII address searched for */
};

static const char *main_restart_names[] = {
  "", ":R", ":V", ":RV", ":A", ":RA", ":VA", ":RVA", ":B", ":BR", ":BV", ":BRV", ":BA", ":BRA", ":BVA", ":BRVA"
};

#define MAIN_CASE_OPERATION_NAME(search) (search)->alpha > MAIN_MODE_PASSIVE || (search)->beta > MAIN_MODE_PASSIVE ? \
                                           (search)->announce ? "ANNOUNCE" : "SEARCH" :                              \
                                           (search)->announce ? "announce" : "search"
#define MAIN_LOOKUP_RESTART_NAME(search) main_restart_names[(search)->restart]
#define MAIN_FORMAT_NAMES(search) \
  MAIN_CASE_OPERATION_NAME (search), (search)->sequence, MAIN_LOOKUP_RESTART_NAME (search)

#define MAIN_CALC_PORT(port, map) ((port) == (map) ? (port) : (port) ? (port) ^ (map) : 0)
#define MAIN_GET_SEARCH(ipport) table_get_pointer (ipport, "") /* search structure is keyed by an empty string in VALUES of main_search_table */

int dht_blacklisted (const struct sockaddr *sa, int salen) {
  unsigned ip = ntohl (((const struct sockaddr_in *) sa)->sin_addr.s_addr);
  simtype key = pointer_new_len (&ip, sizeof (ip));
  if (main_cpu_count >= main_cpu_table.len * 2 || table_get_key_number (main_cpu_table, key) & 1)
    return true;
  return param_get_number ("main.blacklist") > 0 && table_get_key (main_blacklist_table, key).typ != SIMNIL;
}

int dht_gettimeofday (struct timeval *tv, void *tz) {
  tv->tv_sec = (unsigned) (system_get_tick () / 1000), tv->tv_usec = 0; /* tv_usec not used by caller */
  return 0;
}

int dht_random_bytes (void *buf, size_t size) {
  random_get (random_public, pointer_new_len (buf, size));
  return 1;
}

void dht_hash (void *hash_return, int hash_size,
               const void *v1, int len1, const void *v2, int len2, const void *v3, int len3) {
  simtype hash1 = pointer_new_len (v1, len1), hash2 = pointer_new_len (v2, len2);
  simtype hash = sim_crypt_md_hash (sim_crypt_md_new (CRYPT_MD_RIPEMD), hash1, hash2, pointer_new_len (v3, len3));
  if ((unsigned) hash_size > hash.len) {
    memset ((char *) hash_return + hash.len, 0, hash_size - hash.len);
    memcpy (hash_return, hash.str, hash.len);
  } else
    memcpy (hash_return, hash.str, hash_size);
  string_free (hash);
}

int dht_sendto (int sockfd, const void *buf, int len, int flags, const struct sockaddr *to, int tolen) {
  int ret;
  socket_set_qos (&socket_udp_sock, to, 0);
  ret = sendto (sockfd, buf, len, flags, to, tolen);
  if ((main_send_ok_flag = ret >= 0) == false) {
    errno = socket_get_errno ();
    main_send_error_flag = true;
  } else
    main_stats[CONTACT_STATS_CLIENT][CONTACT_STAT_SENT] += len + SOCKET_CALC_OVERHEAD_UDP ((unsigned) len);
  if (ret != len)
    LOG_XTRA_ ("sendto $%d error %d (length %d/%d)\n", sockfd, ret < 0 ? errno : 0, ret < 0 ? 0 : ret, len);
  return ret;
}

static simbool main_blacklist (unsigned ip, const simbyte *key) {
  unsigned i;
  simnumber val = 0;
  for (i = 0; i < sizeof (val); i++)
    val = val << 8 | key[i];
  if (table_add_key_number (main_blacklist_table, string_copy_len (&ip, sizeof (ip)), val).typ != SIMNIL)
    return false;
  if (main_blacklist_buffer[main_blacklist_index])
    LOG_DEBUG_ ("unblacklisting ip %s\n", network_convert_ip (main_blacklist_buffer[main_blacklist_index]));
  table_delete_key (main_blacklist_table, pointer_new_len (&main_blacklist_buffer[main_blacklist_index], sizeof (ip)));
  main_blacklist_buffer[main_blacklist_index] = ip;
  if (++main_blacklist_index >= main_blacklist_size)
    main_blacklist_index = 0;
  main_blacklist_nodes++;
  return true;
}

static simtype main_get_blacklist (simbool save) {
  simtype array = array_new_strings (main_blacklist_size * 2), str;
  int i = main_blacklist_index;
  array.len = 0;
  if (main_blacklist_buffer && table_count (main_blacklist_table))
    do {
      unsigned ip = main_blacklist_buffer[i];
      if (ip) {
        simunsigned id = table_get_key_number (main_blacklist_table, pointer_new_len (&ip, sizeof (ip)));
        ip = htonl (ip);
        array.arr[++array.len] = string_copy_len (&ip, sizeof (ip));
        for (str = string_copy_len (&id, sizeof (id)); save && str.len; id >>= 8)
          str.str[--str.len] = (simbyte) id;
        str.len = sizeof (id);
        array.arr[++array.len] = str;
      }
      if (++i >= main_blacklist_size)
        i = 0;
    } while (i != main_blacklist_index);
  return array;
}

static int main_get_nodes_status (int good, int dubious, int incoming) {
  int listening = main_dht_listening_value;
  main_dht_listening_value = main_dht_listening_threshold;
  if (main_dht_running_flag != true)
    return MAIN_DHT_STOPPED;
  if (MAIN_CHECK_DISCONNECTED (good, dubious))
    return MAIN_DHT_DISCONNECT;
  if (good < MAIN_DHT_CONNECTED_NODES)
    return MAIN_DHT_CONNECTING;
  if (incoming < listening)
    return MAIN_DHT_CONNECTED;
  main_dht_listening_value >>= 1;
  return MAIN_DHT_LISTENING;
}

int main_get_nodes (int *good, int *dubious, int *incoming, int *blocked, int *blacklisted) {
  static int main_warn_nodes[4] = { 0, 0, 0, 0 };
  int nodes[4];
  dht_nodes (AF_INET, &nodes[0], &nodes[1], &nodes[2], &nodes[3]);
  if (good) {
    *good = nodes[0], *dubious = nodes[1], *incoming = nodes[3];
    if (LOG_CHECK_LEVEL_ (SIM_LOG_INFO) && ! main_down_tick && *good >= MAIN_DHT_CONNECTED_NODES) {
      if (*dubious >= *good * (main_dht_warn_flag ? 2 : 4)) {
        if (memcmp (main_warn_nodes, nodes, sizeof (nodes))) {
          memcpy (main_warn_nodes, nodes, sizeof (nodes));
          LOG_ANY_ (main_dht_warn_flag ? SIM_LOG_XTRA : SIM_LOG_INFO,
                    "nodes %d good, %d dubious, %d cached, %d incoming\n",
                    main_warn_nodes[0], main_warn_nodes[1], main_warn_nodes[2], main_warn_nodes[3]);
        }
        main_dht_warn_flag = true;
      } else if (main_dht_warn_flag) {
        LOG_INFO_ ("nodes %d good, %d dubious, %d cached, %d incoming\n", nodes[0], nodes[1], nodes[2], nodes[3]);
        memset (main_warn_nodes, 0, sizeof (nodes));
        main_dht_warn_flag = false;
      }
    }
  }
  if (blocked) {
    simwalker ctx;
    simtype val;
    *blocked = 0;
    if (main_cpu_table.ptr)
      for (table_walk_first (&ctx, main_cpu_table); (val = table_walk_next_number (&ctx, NULL)).typ != SIMNIL;)
        *blocked += (int) val.num & 1;
  }
  if (blacklisted) {
    *blacklisted = 0;
    if (main_blacklist_table.tbl && param_get_number ("main.blacklist") > 0)
      *blacklisted = table_count (main_blacklist_table);
  }
  return main_get_nodes_status (nodes[0], nodes[1], nodes[3]);
}

simnumber main_get_stat (unsigned i, unsigned j) {
  simcustomer proxy;
  simnumber n = 0;
  if (i == CONTACT_STATS_CLIENTS || i == CONTACT_STATS_CLIENT) {
    if (i == CONTACT_STATS_CLIENT && j == CONTACT_STAT_DURATION && main_stat_tick)
      n = system_get_tick () - main_stat_tick;
    return main_stats[i][j] + n;
  }
  if (i == CONTACT_STATS_INPUT) {
    n = proxy_get_stat_contact (NULL, j);
  } else if (i == CONTACT_STATS_OUTPUT && (proxy = proxy_get_proxy (NULL, NULL, NULL)) != NULL)
    n = socket_get_stat (proxy->sock, j, false);
  return n + proxy_get_stat (i - CONTACT_STATS_MINE + PROXY_STATS_ALL, j);
}

void main_set_tick (simbool set) {
  if (set && param_get_number ("net.tor.port") <= 0) {
    int verify = param_get_number_min ("main.verify", 120);
    if (verify && main_dht_verify_tick) {
      LOG_DEBUG_ ("verify (%lld seconds)\n", (simnumber) (system_get_tick () - main_dht_verify_tick) / 1000);
    } else
      LOG_DEBUG_ ("verify (+%d seconds)\n", verify);
    main_dht_verify_tick = verify ? system_get_tick () + verify * 1000 : 0;
  } else
    main_dht_verify_tick = 0;
}

int main_set_port (int fd, int port) {
  if (fd < 0) {
    LOG_ERROR_ ("bad socket $%d\n", fd); /* impossible */
    return EBADF;
  }
  close_socket (socket_udp_sock.fd);
  socket_set_udp (fd);
  main_dht_port = port;
  dht_set_socket (fd, -1);
  return SIM_OK;
}

void sim_set_table_number (simtype *table, simtype key, simnumber number) {
  if (table_set_key_number (*table, key, number).typ != SIMNIL && table_count (*table) > table->len) {
    unsigned count = 0;
    simtype tmp, val, newtable = table_new (table->len);
    simwalker ctx;
    number = 0;
    for (table_walk_first (&ctx, *table); (val = table_walk_next_number (&ctx, NULL)).typ != SIMNIL;)
      if (val.num > number)
        number = val.num;
    for (table_walk_first (&ctx, *table); (val = table_walk_next_number (&ctx, NULL)).typ != SIMNIL;)
      count += val.num == number;
    if (count * 2 <= table->len)
      for (table_walk_first (&ctx, *table); (val = table_walk_next_number (&ctx, &tmp)).typ != SIMNIL;)
        if (val.num == number)
          table_set_key_number (newtable, string_copy_string (tmp), number);
    table_free (*table);
    *table = newtable;
  }
}

static void main_reset_blacklisting (void) {
  unsigned i;
  for (i = 1; i <= contact_list.array.len; i++) {
    simcontact contact = contact_list.array.arr[i].ptr;
    contact->dht.tick = contact->dht.timeout = contact->dht.count = 0;
  }
}

static void event_test_net_status (int sigup) {
  int up = sigup ? sigup > 0 : -1;
  main_dht_reinit_flag = false;
  if (main_dht_state != up) {
    if (up < 0)
      up = 0;
    if (main_dht_state != up) {
      if (simself.flags & SIM_STATUS_FLAG_DHT_IN)
        simself.flags &= ~SIM_STATUS_FLAG_UDP_IN;
      simself.flags &= ~(SIM_STATUS_FLAG_DHT_OUT | SIM_STATUS_FLAG_DHT_IN);
      if (up > 0) {
        simself.flags |= SIM_STATUS_FLAG_DHT_OUT;
        if (! proxy_check_required (true))
          client_cancel_proxy (SIM_PROXY_NOT_NEEDED);
        if (main_get_status () == MAIN_DHT_LISTENING)
          simself.flags |= SIM_STATUS_FLAG_DHT_IN | SIM_STATUS_FLAG_UDP_IN;
        if (main_dht_verify_tick)
          main_set_tick (true);
      } else
        main_reset_blacklisting ();
      LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
      main_dht_state = up;
      client_logon (up <= 0);
      event_send_name (NULL, SIM_EVENT_NET, SIM_EVENT_NET_STATUS, number_new (sigup));
    }
  }
}

static void event_test_net_dht (int status) {
  if (main_dht_status != (status -= MAIN_DHT_DISCONNECT))
    event_send_name (NULL, SIM_EVENT_NET, SIM_EVENT_NET_DHT, number_new (main_dht_status = status));
}

static simbool event_test_net_limit (int error, int threshold) {
  int cpuevent = param_get_number ("limit.cpu.event");
  if (cpuevent > threshold)
    event_send_name (NULL, SIM_EVENT_NET, SIM_EVENT_NET_LIMIT, number_new (main_cpu_error = error));
  return cpuevent > 1;
}

static void main_cputime_init (void) {
  int burst = param_get_number ("main.cpu.burst"), cpuload = param_get_number ("main.cpu.factor") * burst;
  int cpumin = param_get_number ("main.cpu.min") * 1000, cpumax = param_get_number ("main.cpu.max") * 1000;
  simnumber load, count = limit_cpu[LIMIT_CPU_BOGO], rate = limit_cpu[LIMIT_CPU_RATE];
  main_cpu_tick = system_get_tick ();
  main_cpu_load = limit_cpu[LIMIT_CPU_LOAD] * cpuload / count;
  if ((load = main_cpu_load * 100 / (burst * rate)) < cpumin)
    main_cpu_load = (load = cpumin) * burst * rate / 100;
  if (load > cpumax)
    main_cpu_load = (load = cpumax) * burst * rate / 100;
  LOG_DEBUG_ ("speed*%lld %llu.%03llu us/bogohash %d.%03d%%\n", count, cpuload ? main_cpu_load / cpuload / 1000 : 0,
              cpuload ? main_cpu_load / cpuload % 1000 : 0, (int) load / 1000, (int) load % 1000);
}

#if HAVE_LIBPTH || ! (defined(_WIN32) || defined(RUSAGE_THREAD) || defined(CLOCK_THREAD_CPUTIME_ID))
static simunsigned main_cputime_subtract (simunsigned oldcpu, simunsigned newcpu, unsigned factor) {
  if (! factor)
    return limit_set_cpu (oldcpu, newcpu);
  if (! oldcpu)
    return limit_cpu[LIMIT_CPU_HASH] * factor;
  if ((oldcpu = newcpu - oldcpu) == 0)
    oldcpu = MAIN_CPU_TIMER_RESOLUTION;
  if ((newcpu = limit_cpu[LIMIT_CPU_HASH] * factor) < oldcpu)
    oldcpu = newcpu;
  return oldcpu;
}
#else
#define main_cputime_subtract(oldcpu, newcpu, factor) limit_set_cpu (oldcpu, newcpu)
#endif

int main_cputime_test (simunsigned cpu, unsigned ip, simunsigned tick, unsigned factor) {
  if (main_cpu_load) {
    simtype key;
    simunsigned nsec, cputick = 0;
    if (main_cpu_count < main_cpu_table.len * 2) {
      if ((nsec = table_get_key_number (main_cpu_table, key = pointer_new_len (&ip, sizeof (ip)))) == 0)
        key = string_copy_len (&ip, sizeof (ip));
      cputick = sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL);
      main_cpu_recv += cpu = main_cputime_subtract (cpu, cputick, factor);
      if (table_set_key_number (main_cpu_table, key, nsec + cpu).typ == SIMNIL)
        if (++main_cpu_count == main_cpu_table.len * 2) {
          int level = event_test_net_limit (SIM_LIMIT_DHT_DISABLE, -1) ? SIM_LOG_ERROR : SIM_LOG_WARN;
          LOG_ANY_ (level, "speed %u nodes in %lld ms (%lld/%lld us)\n", main_cpu_count, tick - main_cpu_tick,
                    main_cpu_recv / 1000, main_cpu_load * (tick - main_cpu_tick) / (main_cpu_burst * 1000));
        }
    } else
      main_cpu_recv += main_cputime_subtract (cpu, cputick = sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL), factor);
    if (main_cpu_recv > main_cpu_load && tick - main_cpu_tick < main_cpu_burst) {
      simwalker ctx;
      unsigned *ips, i = 0, j, k = 0;
      simunsigned *array = sim_new ((sizeof (*array) + sizeof (*ips)) * main_cpu_count);
      simunsigned cpuspeed = main_cpu_load * (tick - main_cpu_tick) / main_cpu_burst, cpurecv = main_cpu_recv;
      int l = (main_cpu_error != SIM_LIMIT_DHT_ENABLE) * 2;
      ips = (unsigned *) (array + main_cpu_count);
      for (table_walk_first (&ctx, main_cpu_table); i < main_cpu_count; i++)
        if ((array[k] = table_walk_next_number (&ctx, &key).num) > 1)
          ips[k++] = *(unsigned *) key.ptr;
      for (i = 1; i < k; i++)
        for (j = i; j && array[j] > array[j - 1]; j--) {
          ip = ips[j];
          nsec = array[j];
          array[j] = array[j - 1];
          ips[j] = ips[j - 1];
          array[j - 1] = nsec;
          ips[j - 1] = ip;
        }
      for (cpu = i = 0; i < k; i++) {
        table_set_key_number (main_cpu_table, pointer_new_len (&ips[i], sizeof (ips[i])), 1);
        cpu += nsec = array[i];
        if ((main_cpu_recv -= nsec) <= cpuspeed)
          break;
      }
      sim_free (array, (sizeof (*array) + sizeof (*ips)) * main_cpu_count);
      cputick = limit_set_cpu (cputick, sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL));
      nsec = main_cpu_burst;
      main_cpu_burst += (unsigned) (param_get_number ("main.cpu.burst") * 1000 * (cputick + cpu) / main_cpu_load);
      l = event_test_net_limit (SIM_LIMIT_DHT_BLACKLIST, l) ? SIM_LOG_ERROR : SIM_LOG_WARN;
      LOG_ANY_ (l, "speed %lld us in %lld ms (%d/%d nodes, %u/%u searches) burst +%lld ms (blacklisting %d/%d nodes)\n",
                cpurecv / 1000, tick - main_cpu_tick, main_cpu_count, main_cpu_table.len * 2,
                table_count (main_search_table), main_dht_size, main_cpu_burst - nsec, i + 1, k);
      return (int) (main_cpu_burst - (tick - main_cpu_tick));
    }
  } else
    main_cpu_recv += limit_set_cpu (cpu, sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL));
  return 0;
}

static void main_cputime_reset (simunsigned tick) {
  unsigned burst = param_get_number ("main.cpu.burst") * 1000, len = main_cpu_table.len;
  LOG_ANY_ (main_cpu_burst != burst || main_cpu_count >= len * 2 ? SIM_LOG_WARN : SIM_LOG_XTRA,
            "speed RESET in %lld ms (%d/%d nodes, %lld/%lld us)\n", tick - main_cpu_tick, main_cpu_count,
            len * 2, main_cpu_recv / 1000, main_cpu_load * (tick - main_cpu_tick) / (main_cpu_burst * 1000));
  if (main_cpu_error != SIM_LIMIT_DHT_ENABLE)
    event_test_net_limit (SIM_LIMIT_DHT_ENABLE, -1);
  table_free (main_cpu_table), main_cpu_table = nil ();
  main_cpu_tick = tick;
  main_cpu_send = main_cpu_recv = main_cpu_count = 0;
  main_cpu_table = table_new_rand ((main_cpu_burst = burst) / 25);
}

static simbool main_search_check_blacklisting (const simtype key, simcontact contact) {
  if (! contact)
    return false;
  return ! memcmp (key.str + key.len - sizeof (contact->dht.rnd), contact->dht.rnd, sizeof (contact->dht.rnd));
}

static void main_search_destroy (struct main_search *search, simtype ipport, simcontact contact) {
  simwalker ctx;
  int proxyport = 0;
  unsigned proxyip = 0, ip = 0;
  simtype key, val;
  if (! proxy_get_ip_proxy (NULL, &proxyip, &proxyport) || ! main_dht_verify_tick)
    search->restart &= ~MAIN_RESTART_VERIFY;
  if (! (search->restart & MAIN_RESTART_ONCE) && table_walk_first (&ctx, ipport))
    while ((val = table_walk_next (&ctx, &key)).typ != SIMNIL)
      if (val.typ == SIMNUMBER) {
        struct _network_ip_port *node = key.ptr;
        int port = ntohs (node->port);
        port = MAIN_CALC_PORT (port, search->map);
        LOG_XTRA_ ("%s%u%s found port %s:%d\n", MAIN_FORMAT_NAMES (search),
                   network_convert_ip (ntohl (node->ip)), port);
        if (search->announce && ntohs (node->port) == search->announce && (! ip || ntohl (node->ip) == ip)) {
          simnumber count;
          ip = ntohl (node->ip);
          count = table_get_key_number (simself.ipdht, pointer_new_len (&ip, sizeof (ip)));
          sim_set_table_number (&simself.ipdht, string_copy_len (&ip, sizeof (ip)), count + 1);
          search->restart &= ~MAIN_RESTART_ANNOUNCE;
        }
        if (proxyport && port == proxyport && ntohl (node->ip) == proxyip) {
          main_set_tick (true);
          search->restart &= ~MAIN_RESTART_VERIFY;
        }
      }
  LOG_DEBUG_ ("done %s%u%s '%s'\n", MAIN_FORMAT_NAMES (search), CONTACT_GET_NICK (search->addr));
  if ((search->restart & (MAIN_RESTART_VERIFY | MAIN_RESTART_ONCE)) == MAIN_RESTART_VERIFY) {
    if (! param_get_number ("main.verify") || param_get_number ("net.tor.port") > 0) {
      main_set_tick (false);
      search->restart &= ~MAIN_RESTART_VERIFY;
    } else if (main_dht_state > 0 && system_get_tick () >= main_dht_verify_tick) {
      client_cancel_proxy (SIM_PROXY_DROPPED);
      search->restart &= ~MAIN_RESTART_VERIFY;
    }
  }
  if ((search->restart & (MAIN_RESTART_ANNOUNCE | MAIN_RESTART_ONCE)) == MAIN_RESTART_ANNOUNCE) {
    if (! search->announce) {
      search->restart &= ~MAIN_RESTART_ANNOUNCE;
    } else if (contact != contact_list.me && contact != &main_proxy_contact && param_get_number ("main.reannounce")) {
      if (! (search->restart &= ~MAIN_RESTART_ANNOUNCE)) {
        proxy_customer_announce (proxy_find_server (search->addr));
      } else
        search->restart |= MAIN_RESTART_ANNOUNCE;
    }
  }
  search->restart &= ~MAIN_RESTART_BLACKLISTED;
  if (main_dht_recursed_flag) {
    LOG_XTRA_ ("restart %s%u%s recursed '%s'\n", MAIN_FORMAT_NAMES (search), CONTACT_GET_NICK (search->addr));
  } else if (search->restart & MAIN_RESTART_ONCE || (search->restart && param_get_number ("main.reannounce"))) {
    int ret = main_search_create (search->addr, search->sequence, search->mode, search->restart | MAIN_RESTART_SEARCH);
    if (ret == MAIN_SEARCH_SKIP) {
      LOG_DEBUG_ ("restart %s%u%s skipped '%s'\n", MAIN_FORMAT_NAMES (search), CONTACT_GET_NICK (search->addr));
    } else if (ret != MAIN_SEARCH_NEW) {
      LOG_WARN_ ("restart %s%u%s failed '%s'\n", MAIN_FORMAT_NAMES (search), CONTACT_GET_NICK (search->addr));
    } else
      LOG_DEBUG_ ("restarted %s%u%s '%s'\n", MAIN_FORMAT_NAMES (search), CONTACT_GET_NICK (search->addr));
  }
  table_free (ipport);
}

static simbool main_search_test_found (simcontact contact, simtype ipport,
                                       const struct main_search *search, const struct _network_ip_port *node) {
  simbool done = false;
  int port = ntohs (node->port), proxyport;
  char ipstr[100];
  if (search) {
    if (contact && strcmp (search->addr, contact->addr))
      LOG_ERROR_ ("%s%u%s unknown '%s'\n", MAIN_FORMAT_NAMES (search), contact->nick.str);
    if (table_set_key_number (ipport, string_copy_len (node, CONTACT_SIZE_IP_PORT), true).num)
      return false;
    LOG_CODE_DEBUG_ (strcpy (ipstr, network_convert_ip (main_dht_sender)));
    LOG_DEBUG_ ("%s%u%s %s found ip %s:%d '%s'\n", MAIN_FORMAT_NAMES (search), ipstr,
                network_convert_ip (ntohl (node->ip)), MAIN_CALC_PORT (port, search->map),
                CONTACT_GET_NICK (search->addr));
  }
  if (contact == &main_proxy_contact || (contact && contact->auth >= CONTACT_AUTH_NEW)) {
    unsigned ip = ntohl (node->ip), proxyip;
    port = MAIN_CALC_PORT (port, contact->dht.id - 1);
    if (! search) {
      LOG_CODE_DEBUG_ (strcpy (ipstr, network_convert_ip (main_dht_sender)));
      LOG_DEBUG_ ("expired %s found ip %s:%d '%s'\n", ipstr, network_convert_ip (ip), port, contact->nick.str);
    } else if (! search->announce && contact == contact_list.me && main_dht_verify_tick)
      if (proxy_get_ip_proxy (NULL, &proxyip, &proxyport) && ip == proxyip && proxyport == port)
        done = true;
    if (ip && port) {
      if (contact != &main_proxy_contact) {
        client_probe (contact, ip, port, (char *) &main_dht_sender, CLIENT_PROBE_DHT);
      } else if (! proxy_get_proxy (NULL, NULL, NULL) && proxy_check_required (true))
        if (! sim_network_check_local (ip) && ! client_check_local (ip, false))
          proxy_probe ("DHT", ip, port, main_dht_sender);
    }
  }
  return done;
}

static void main_search_callback (void *arg, int event, const unsigned char *info_hash,
                                  const void *data, size_t data_len) {
  struct main_search *search = NULL;
  simcontact contact = arg;
  simtype key = pointer_new_len (info_hash, MAIN_DHT_SIZE_ID), ipport = table_get_key_table (main_search_table, key);
  if (ipport.typ != SIMNIL)
    search = MAIN_GET_SEARCH (ipport);
  if (event == DHT_EVENT_SEARCH_DONE) {
    if (search) {
      simunsigned cpu = main_dht_sender ? 0 : sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL);
      if (! (search->restart & MAIN_RESTART_BLACKLISTED) && contact != &main_proxy_contact && ! main_down_tick)
        if (main_search_check_blacklisting (key, contact) && main_get_status () >= MAIN_DHT_CONNECTED) {
          simunsigned tick = system_get_tick (), timeout = contact->dht.timeout;
          if (! timeout) {
            contact->dht.tick = tick;
            contact->dht.timeout = param_get_min ("proxy.announce", 1) * 1000;
          } else if (tick - contact->dht.tick >= timeout) {
            contact->dht.tick = tick;
            contact->dht.timeout = (timeout <<= 1) > 2000000000 ? 2000000000 : (unsigned) timeout;
          }
        }
      main_search_destroy (search, table_detach_key (main_search_table, key), contact);
      main_cpu_send += limit_set_cpu (cpu, sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL));
    } else
      LOG_ERROR_SIMTYPE_ (key, LOG_BIT_BIN, "done unknown '%s' ", CONTACT_GET_NICK (contact ? contact->addr : NULL));
  } else if (event == DHT_EVENT_VALUES) {
    if (! main_search_check_blacklisting (key, contact)) {
      simbool done = false;
      while ((int) data_len > 0) {
        struct _network_ip_port node;
        memcpy (&node, data, CONTACT_SIZE_IP_PORT);
        done |= main_search_test_found (contact, ipport, search, &node);
        data = (const char *) data + CONTACT_SIZE_IP_PORT;
        data_len -= CONTACT_SIZE_IP_PORT;
      }
      if (done)
        main_search_denounce (contact->addr);
    } else {
      if (search)
        search->restart |= MAIN_RESTART_BLACKLISTED;
      main_reset_blacklisting (); /* contact->dht.timeout = 0; */
      if (param_get_number ("main.blacklist") > 0 && main_blacklist (main_dht_sender, key.ptr)) {
        if (search) {
          LOG_DEBUG_ ("%s%u%s blacklisting ip %s '%s'\n", MAIN_FORMAT_NAMES (search),
                      network_convert_ip (main_dht_sender), CONTACT_GET_NICK (search->addr));
        } else
          LOG_DEBUG_ ("expired blacklisting ip %s '%s'\n", network_convert_ip (main_dht_sender), contact->nick.str);
      }
    }
  }
}

int main_search_create (const char *address, unsigned sequence, int mode, int restart) {
  simbool announce, restarted = (restart & MAIN_RESTART_SEARCH) != 0;
  simunsigned cpu = restarted ? 0 : sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL);
  int ret = MAIN_SEARCH_NEW, i, count = 0, salt = htonl (0);
  int port = (unsigned short) network_get_port (NULL);
  simtype ipport, key = nil (), dht;
  struct main_search search;
  simbyte dhtid[RANDOM_SIZE_HASH];
  simcontact contact = contact_list_find_address (strncpy (search.addr, address, CONTACT_ADDRESS_SIZE));
  search.addr[CONTACT_ADDRESS_SIZE - 1] = 0; /* just in case */
  if (contact) {
    count = contact->dht.count;
    if (contact->dht.search && contact->dht.active)
      mode = MAIN_MODE_ACTIVE;
  }
  if (main_dht_running_flag != true || (contact == contact_list.me && param_get_number ("net.tor.port") > 0))
    goto done;
  if (strcmp (address, PROXY_ADDRESS_PROXY)) {
    if (contact_list.test & 0x20000000)
      goto done;
    key = sim_contact_convert_address (address, 'S');
    dht = sim_crypt_md_hash (sim_crypt_md_new (CRYPT_MD_WHIRLPOOL), key, nil (), nil ());
    memcpy (dhtid, dht.str, dht.len); /* dht.len == sizeof (dhtid) */
    string_free (dht);
    if (contact == contact_list.me) {
      announce = simself.status != SIM_STATUS_OFF; /* no announce means no search in this case */
    } else
      announce = proxy_find_server (address) != NULL;
    if (! announce && simself.status == SIM_STATUS_OFF)
      goto done;
    string_free (key);
  } else {
    contact = &main_proxy_contact;
    announce = ! proxy_check_required (true);
    strcpy (memset (dhtid, 0, sizeof (dhtid)), "proxy dht channel #0");
  }
  search.announce = 0;
  search.map = dhtid[0] << 8 | dhtid[1];
  if (! port)
    port = (unsigned short) main_dht_port;
  restart &= ~MAIN_RESTART_SEARCH;
  if ((! announce || (search.announce = MAIN_CALC_PORT (port, search.map)) == 0) && restart & ~MAIN_RESTART_ONCE) {
    main_cpu_send += limit_set_cpu (cpu, sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL));
    return MAIN_SEARCH_SKIP;
  }
  if (restart & MAIN_RESTART_ONCE) {
    restart &= ~MAIN_RESTART_ONCE;
  } else if (contact && (i = param_get_number ("main.blacklist")) > 0)
    if (mode == MAIN_MODE_ACTIVE || ++count >= i || system_get_tick () - contact->dht.tick >= contact->dht.timeout) {
      count = 0;
      restart |= MAIN_RESTART_ONCE;
    }
  if (! (restart & ~MAIN_RESTART_ONCE) && search.announce)
    restart |= (main_dht_verify_tick && contact == contact_list.me ? MAIN_RESTART_VERIFY : 0) | MAIN_RESTART_ANNOUNCE;
  search.restart = restart;
  search.sequence = sequence;
  search.mode = mode;
  search.alpha = mode == MAIN_MODE_ACTIVE ? param_get_number ("main.dht.alpha") : mode;
  search.beta = mode == MAIN_MODE_ACTIVE ? param_get_number ("main.dht.beta") : mode;
  key = pointer_new_len (&salt, sizeof (salt));
  key = sim_crypt_md_hash (sim_crypt_md_new (CRYPT_MD_RIPEMD), pointer_new_len (dhtid, sizeof (dhtid)), nil (), key);
  ret = MAIN_SEARCH_FAILED;
  announce = true;
  ipport = table_get_key_table (main_search_table, key); /* find old white search */
  if (contact) {
    if (! contact->dht.id) {
      simunsigned bits = random_get_number (random_public, ((simunsigned) 1 << param_get_number ("main.bits")) - 2) + 1;
      memcpy (contact->dht.rnd, key.str + MAIN_DHT_SIZE_ID - sizeof (contact->dht.rnd), sizeof (contact->dht.rnd));
      for (i = sizeof (contact->dht.rnd); i--; bits >>= 8)
        contact->dht.rnd[i] ^= (simbyte) bits;
      contact->dht.id = search.map + 1;
    }
    dht = string_copy_string (key);
    memcpy (dht.str + MAIN_DHT_SIZE_ID - sizeof (contact->dht.rnd), contact->dht.rnd, sizeof (contact->dht.rnd));
    if (restart & MAIN_RESTART_ONCE) {
      if (sequence || ipport.typ != SIMNIL) {
        if (ipport.typ == SIMNIL && main_search_create (address, sequence, mode, restart) == MAIN_SEARCH_FAILED) {
          string_free (dht);
          goto done;
        } /* black search runs in parallel to white search in this case, so it will not need to restart */
        search.restart &= MAIN_RESTART_BLACKLISTED;
      }
      memcpy (key.str, dht.str, dht.len);
      announce = false;
    }
    if (ipport.typ == SIMNIL && (ipport = table_get_key_table (main_search_table, dht)).typ != SIMNIL) {
      struct main_search *oldsearch = MAIN_GET_SEARCH (ipport); /* found old black search: set its restart behavior */
      if (search.restart & MAIN_RESTART_ONCE) {
        oldsearch->restart |= MAIN_RESTART_ONCE;
      } else
        oldsearch->restart &= MAIN_RESTART_BLACKLISTED;
    }
    string_free (dht);
  }
  if (ipport.typ != SIMNIL) {
    struct main_search *oldsearch = MAIN_GET_SEARCH (ipport);
    if (sequence && (! oldsearch->sequence || oldsearch->sequence > sequence))
      oldsearch->sequence = sequence;
    /* same search in progress. repeating it all the time will prevent it from ever expiring.
       so wait, unless active mode or converting an old search to announcement */
    if (mode != MAIN_MODE_ACTIVE && (! search.announce || oldsearch->announce)) {
      ret = MAIN_SEARCH_OLD;
      goto done;
    }
    oldsearch->mode = mode;
    oldsearch->alpha = search.alpha;
    oldsearch->beta = search.beta;
    if (search.announce) {
      oldsearch->announce = search.announce;
    } else if (oldsearch->announce)
      search.announce = MAIN_CALC_PORT (port, search.map);
    search.sequence = oldsearch->sequence;
    LOG_DEBUG_ ("re-%s%u%s '%s'\n", MAIN_FORMAT_NAMES (&search), CONTACT_GET_NICK (address));
  }
  if ((ipport = table_get_key_table (main_search_table, key)).typ == SIMNIL) {
    if (! restarted) {
      if (main_cpu_load) {
        simunsigned cpuspeed = main_cpu_load * (system_get_tick () - main_cpu_tick) / main_cpu_burst;
        if (main_cpu_recv + main_cpu_send > cpuspeed * param_get_number ("main.cpu.search") / 100)
          goto done;
      }
      if ((int) table_count (main_search_table) > main_dht_size / param_get_number ("main.searches"))
        goto done;
    }
    table_add_key (main_search_table, key, ipport = table_new (restart & MAIN_RESTART_ONCE ? 1 : 11));
    table_add (ipport, "", string_copy_len (&search, sizeof (search)));
    key = pointer_new_len (key.str, key.len);
    ipport = nil ();
  }
  LOG_DEBUG_ ("%s %s%u%s '%s'\n", ipport.typ == SIMNIL ? "start" : "started", MAIN_FORMAT_NAMES (&search),
              CONTACT_GET_NICK (address));
  main_dht_recursed_flag = true;
  if (dht_search_param (key.str, announce ? search.announce : 0, AF_INET,
                        main_search_callback, contact, search.alpha, search.beta) < 0) {
    LOG_WARN_ ("%s%u%s failed '%s'\n", MAIN_FORMAT_NAMES (&search), CONTACT_GET_NICK (address));
    table_delete_key (main_search_table, key);
  } else
    ret = MAIN_SEARCH_NEW;
  main_dht_recursed_flag = false;
done:
  string_free (key);
  if (contact && ret == MAIN_SEARCH_NEW) {
    contact->dht.search = 0;
    contact->dht.active = false;
    contact->dht.count = count;
  }
  main_cpu_send += limit_set_cpu (cpu, sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL));
  return ret;
}

simbool main_search_cancel (const char *address, unsigned sequence, int cancel) {
  simbool notdone = cancel != MAIN_CANCEL_CHECK;
  simtype key, val;
  simwalker ctx;
  simunsigned cpu = sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL);
  simcontact contact = &main_proxy_contact;
  if (strcmp (address, PROXY_ADDRESS_PROXY))
    contact = contact_list_find_address (address);
  if (main_dht_running_flag == true && table_walk_first (&ctx, main_search_table))
    while ((val = table_walk_next_table (&ctx, &key)).typ != SIMNIL) {
      struct main_search *search = MAIN_GET_SEARCH (val);
      unsigned seq = search->sequence;
      if (! strcmp (search->addr, address) && (! sequence || (seq && seq <= sequence))) {
        if (cancel == MAIN_CANCEL_CHECK)
          return true;
        if ((! search->announce && strcmp (address, contact_list.me->addr)) || cancel == MAIN_CANCEL_DENOUNCE) {
          const char *operation = MAIN_CASE_OPERATION_NAME (search), *restart = MAIN_LOOKUP_RESTART_NAME (search);
          LOG_DEBUG_ ("cancelling %s%u%s '%s'\n", operation, seq, restart, CONTACT_GET_NICK (address));
          if (! main_search_check_blacklisting (key, contact)) {
            search->restart = search->announce = 0; /* prevent restart in main_search_destroy */
            main_dht_sender = 1;
            if (! dht_end_search (key.str, AF_INET, main_search_callback, NULL)) {
              LOG_WARN_ ("cancelled %s%u%s too late '%s'\n", operation, seq, restart, CONTACT_GET_NICK (address));
              table_delete_key (main_search_table, key);
            } else /* dht_end_search just called main_search_destroy so search is already freed */
              LOG_DEBUG_ ("cancelled %s%u%s '%s'\n", operation, seq, restart, CONTACT_GET_NICK (address));
            main_dht_sender = 0;
            table_walk_first (&ctx, main_search_table); /* element got deleted, so restart */
          } else
            search->restart &= MAIN_RESTART_BLACKLISTED;
        }
        notdone = false;
      }
    }
  if (notdone && (! contact || ! contact->dht.search))
    LOG_DEBUG_ ("cancelled%u not in progress '%s'\n", sequence, CONTACT_GET_NICK (address));
  if (contact && cancel != MAIN_CANCEL_CHECK) {
    contact->dht.search = 0;
    contact->dht.active = false;
  }
  main_cpu_send += limit_set_cpu (cpu, sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL));
  return false;
}

static void main_init_stat (simbool up) {
  if (up) {
    main_stat_tick = system_get_tick ();
    main_stats[CONTACT_STATS_CLIENT][CONTACT_STAT_COUNT]++;
  } else if (main_stat_tick) {
    main_stats[CONTACT_STATS_CLIENT][CONTACT_STAT_DURATION] += system_get_tick () - main_stat_tick;
    main_stat_tick = 0;
    LOG_DEBUG_ ("sessions %lld seconds (%lld : %lld)\n", main_stats[CONTACT_STATS_CLIENT][CONTACT_STAT_DURATION] / 1000,
                main_stats[CONTACT_STATS_CLIENT][CONTACT_STAT_RECEIVED],
                main_stats[CONTACT_STATS_CLIENT][CONTACT_STAT_SENT]);
  }
}

void main_recv_udp (void *tosleep, const simbyte *input, int length, const void *sin, int sinlen) {
  unsigned timeout = param_get_number_min ("main.timeout", param_get_default_number ("main.timeout", 0) / 2), n;
  int fd, ret = MAIN_SEARCH_NEW, announce = param_get_number_min ("main.announce", param_get_min ("proxy.announce", 0));
  /*LOG_XTRA_ ("recv %d bytes (sleep %d)\n", length, (int) *(time_t *) tosleep);*/
  if (main_dht_running_flag == true) {
    simbool revive = false;
    simunsigned tick = system_get_tick (), cpu;
    if (length >= 0)
      dht_periodic (NULL, 0, NULL, 0, tosleep, main_search_callback, NULL);
    if (tick - main_cpu_tick >= main_cpu_burst)
      main_cputime_reset (tick);
    if (length > 0) {
      main_stats[CONTACT_STATS_CLIENT][CONTACT_STAT_RECEIVED] += length + SOCKET_CALC_OVERHEAD_UDP ((unsigned) length);
      if (main_down_tick) {
        int err = network_periodic (main_down_flag ? NETWORK_CMD_RESTART : NETWORK_CMD_START);
        LOG_NOTE_ ("%s (data received after %lld seconds)\n",
                   main_down_flag ? "reconnected" : "reconnecting", (tick - main_dht_recv_tick) / 1000);
        if (main_down_flag) {
          if (err != SIM_OK) {
            main_reinit ();
            main_ssl_reinit_flag = true;
          }
          main_init_stat (true);
        }
        main_dht_logon ();
        main_down_tick = 0;
        main_down_flag = true;
      }
      main_dht_sender = ntohl (((const struct sockaddr_in *) sin)->sin_addr.s_addr);
      cpu = sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL);
      dht_periodic (input, length, sin, sinlen, tosleep, main_search_callback, NULL);
      main_cputime_test (cpu, main_dht_sender, main_dht_recv_tick = tick, 0);
      main_dht_sender = 0;
    }
    if (! main_down_tick && timeout && tick - main_dht_recv_tick > timeout * 1000) {
      if (! main_dht_recv_tick) {
        main_dht_recv_tick = tick;
      } else
        LOG_DEBUG_ ("disconnecting (no data for %lld seconds)\n", (tick - main_dht_recv_tick) / 1000);
      main_dht_announce_tick = main_down_tick = tick;
      main_dht_mode = MAIN_MODE_ACTIVE;
      main_down_flag = false;
      revive = simself.status == SIM_STATUS_OFF || param_get_number ("net.tor.port") > 0;
    }
    if (! main_down_flag && tick - main_down_tick > timeout * 1000) {
      LOG_NOTE_ ("disconnected (no data for %lld seconds)\n", (tick - main_dht_recv_tick) / 1000);
      main_down_flag = true;
      simself.flags &= ~(SIM_STATUS_FLAG_UDP_IN | SIM_STATUS_FLAG_TCP_IN | SIM_STATUS_FLAG_SSL_IN |
                         SIM_STATUS_FLAG_UDP_OUT | SIM_STATUS_FLAG_TCP_OUT | SIM_STATUS_FLAG_SSL_OUT |
                         SIM_STATUS_FLAG_DNS | SIM_STATUS_FLAG_UPNP);
      simself.oldflags = 0;
      LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
      event_test_net_status (-1);
      main_init_stat (false);
      network_periodic (NETWORK_CMD_START);
    }
    if ((main_down_tick || main_send_error_flag) && *(time_t *) tosleep && main_dht_ip) {
      unsigned ip = sim_network_get_default ();
      if (! ip || ip == main_dht_ip) {
        LOG_XTRA_ ("network interface %s\n", network_convert_ip (ip));
      } else if (socket_listen_udp (ip, main_dht_port, &fd, NULL) == SIM_OK)
        if (main_set_port (fd, main_dht_port) == SIM_OK) {
          main_dht_ip = ip;
          simself.flags &= ~(SIM_STATUS_FLAG_UDP_IN | SIM_STATUS_FLAG_TCP_IN |
                             SIM_STATUS_FLAG_SSL_IN | SIM_STATUS_FLAG_UPNP);
          simself.oldflags = 0;
          LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
          LOG_INFO_ ("network interface %s\n", network_convert_ip (ip));
          if (network_periodic (NETWORK_CMD_RESTART) != SIM_OK) {
            main_reinit ();
            main_ssl_reinit_flag = true;
          }
        }
      main_send_error_flag = false;
    }
    if (announce && tick >= main_dht_announce_tick) {
      if (revive || (! proxy_check_required (true) && param_get_number ("proxy.require") >= -1))
        if (revive || limit_test_server (NULL) == SIM_OK)
          ret = main_search_start (PROXY_ADDRESS_PROXY, 0, main_dht_mode);
      if (ret == MAIN_SEARCH_NEW)
        ret = main_search_start (contact_list.me->addr, 0, main_dht_mode);
      if (ret == MAIN_SEARCH_NEW && (main_down_flag || ! main_down_tick) && main_get_status () > MAIN_DHT_DISCONNECT) {
        main_dht_announce_tick = tick + announce * 1000;
        main_dht_mode = MAIN_MODE_PASSIVE;
      } else
        main_dht_announce_tick = tick + param_get_number ("main.research") * 1000;
    }
    for (n = 1; n <= contact_list.array.len; n++) {
      simcontact contact = contact_list.array.arr[n].ptr;
      if (contact->dht.search && tick >= contact->dht.search) {
        if (ret != MAIN_SEARCH_FAILED)
          ret = main_search_start (contact->addr, 0, contact->dht.active ? MAIN_MODE_ACTIVE : MAIN_MODE_PASSIVE);
        if (ret != MAIN_SEARCH_NEW)
          contact->dht.search = tick + param_get_number ("main.research") * 1000;
      }
    }
    if (ret != MAIN_SEARCH_FAILED)
      proxy_announce (tick, false);
  } else if (length >= 0)
    *(time_t *) tosleep = 60;
}

static unsigned main_dht_count (unsigned *nodes) {
  static int main_last_good = 0, main_last_dubious = 0, main_last_cached = 0, main_last_incoming = 0;
  int good, dubious, cached, incoming;
  dht_nodes (AF_INET, &good, &dubious, &cached, &incoming);
  if (main_last_good != good || main_last_dubious != dubious ||
      main_last_cached != cached || main_last_incoming != incoming) {
    main_last_good = good, main_last_dubious = dubious, main_last_cached = cached, main_last_incoming = incoming;
    LOG_DEBUG_ ("nodes %d good, %d dubious, %d cached, %d incoming\n", good, dubious, cached, incoming);
  }
  if (MAIN_CHECK_DISCONNECTED (good, dubious) ^ main_dht_disconnected_flag) {
    main_dht_disconnected_flag = MAIN_CHECK_DISCONNECTED (good, dubious);
    LOG_NOTE_ ("%s (%d/%d nodes)\n", main_dht_disconnected_flag ? "DISCONNECTED" : "CONNECTING", good, good + dubious);
    if (! main_dht_disconnected_flag) {
      client_logon (false);
    } else
      main_dht_logon ();
  }
  if (nodes)
    *nodes = dubious;
  return good;
}

static void main_dht_sleep_ (void) {
  int err = log_get_error_ ();
  if (err != SIM_OK && simself.status != SIM_STATUS_OFF) {
    sim_error_set_text (" [", FILE_LOG_CORE, "]", err);
    event_test_error (NULL, SIM_EVENT_ERROR_FILE_WRITE, err);
  }
  if (main_ssl_reinit_flag) {
    main_ssl_reinit_flag = false;
    ssl_reinit_ ();
  }
  pth_sleep_ (1);
}

static void main_dht_wait_ (unsigned seconds) {
  unsigned msec = seconds * 1000 / 2 + sim_get_random (seconds * 1000), count = msec / 1000, good, dubious;
  while (main_dht_running_flag == true) {
    if ((good = main_dht_count (&dubious)) >= MAIN_DHT_CONNECTED_NODES && main_send_ok_flag)
      break;
    main_dht_size = good + dubious;
    if (! count--) {
      pth_usleep_ (msec % 1000 * 1000);
      break;
    }
    if (main_send_ok_flag) {
      if (MAIN_CHECK_DISCONNECTED (good, dubious)) {
        if (! main_dht_reinit_flag)
          event_test_net_status (0);
        event_test_net_dht (MAIN_DHT_DISCONNECT);
      } else
        event_test_net_dht (MAIN_DHT_CONNECTING);
    }
    main_dht_sleep_ ();
  }
}

static int main_dht_ping (unsigned ip, int port, const simbyte *id) {
  struct sockaddr_in sin;
  memset (&sin, 0, sizeof (sin));
  sin.sin_family = AF_INET, sin.sin_addr.s_addr = ip, sin.sin_port = (unsigned short) port;
  if (id)
    dht_insert_node (id, (struct sockaddr *) &sin, sizeof (sin));
  LOG_DEBUG_ ("ping %s:%d\n", network_convert_ip (ntohl (ip)), ntohs (port));
  return dht_ping_node ((struct sockaddr *) &sin, sizeof (sin));
}

static simtype main_dht_boot_ (const simtype nodes) {
  int err, port, pinged = 0, first = true;
  unsigned i, j, count = 0, nodecount = 0;
  simtype hosts = array_new_strings (0), ip, hostport, host, ips;
  while (main_dht_running_flag == true) {
    simtype key, val;
    simwalker ctx;
    simunsigned cpu = sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL);
    if (nodes.typ != SIMNIL && table_walk_first (&ctx, nodes))
      while ((val = table_walk_next_string (&ctx, &key)).typ != SIMNIL)
        if (key.len == CONTACT_SIZE_IP_PORT) {
          simbyte *id = val.len == MAIN_DHT_SIZE_ID ? val.str : NULL;
          struct _network_ip_port *node = key.ptr;
          if (main_dht_running_flag != true)
            return hosts;
          if ((count = main_dht_count (NULL)) >= MAIN_DHT_CONNECTED_NODES && main_send_ok_flag) {
          quit:
            if (pinged)
              main_cpu_send += limit_set_cpu (cpu, sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL));
            return hosts;
          }
          if (main_dht_ping (node->ip, node->port, id) < 0)
            LOG_WARN_ ("ping error %d\n", errno);
          if (++pinged > MAIN_DHT_BUCKET) {
            main_cpu_send += limit_set_cpu (cpu, sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL));
            main_dht_wait_ (MAIN_CALC_DELAY (count, MAIN_DHT_BUCKET, 4));
            if (main_dht_running_flag != true)
              return hosts;
            cpu = sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL);
            pinged = 0;
            first = false;
          }
        }
    main_cpu_send += limit_set_cpu (cpu, sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL));
    count = main_dht_count (NULL);
    LOG_DEBUG_ ("nodes count = %d\n", count);
    if (count <= nodecount) {
      if (! first)
        do {
          main_dht_wait_ (MAIN_CALC_DELAY (nodecount = count, MAIN_DHT_BUCKET, 40));
          if (main_dht_running_flag != true)
            return hosts;
          if ((count = main_dht_count (NULL)) >= MAIN_DHT_CONNECTED_NODES && main_send_ok_flag)
            return hosts;
          LOG_DEBUG_ ("new nodes count = %d\n", count);
          first++;
        } while (count > nodecount);
      main_dht_reinit_flag = false;
      if (first == true) {
        if (! hosts.len) {
          for (i = 1; i <= (hostport = param_get_strings ("main.boot.ips")).len; i++) {
            if (main_dht_running_flag != true)
              return hosts;
            if (main_dht_count (NULL) >= MAIN_DHT_CONNECTED_NODES && main_send_ok_flag)
              return hosts;
            host = sim_network_parse_host_port (hostport.arr[i].ptr, &port);
            LOG_INFO_ ("resolve %s:%u\n", host.str, port);
            if (port) {
              if ((err = network_dns_resolve_ (NULL, 0, host.ptr, port, true, &ips)) != SIM_OK)
                LOG_WARN_ ("resolve error %d\n", err);
              for (j = 1; j <= ips.len; j++) {
                struct _network_ip_port *node = (ip = string_new (CONTACT_SIZE_IP_PORT)).ptr;
                node->ip = htonl ((unsigned) ips.arr[j].num), node->port = htons (port);
                array_append (&hosts, ip);
                LOG_INFO_ ("resolved %s:%u\n", network_convert_ip (ntohl (node->ip)), port);
              }
              array_free (ips);
            }
            string_free (host);
          }
          first = ! table_count (nodes);
        }
        if (first) {
          first = false;
          cpu = sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL);
          for (j = 1; j <= hosts.len; j++) {
            struct _network_ip_port *node = hosts.arr[j].ptr;
            if (main_dht_running_flag != true)
              return hosts;
            if ((count = main_dht_count (NULL)) >= MAIN_DHT_CONNECTED_NODES && main_send_ok_flag)
              goto quit;
            if (main_dht_ping (node->ip, node->port, NULL) >= 0) {
              simself.flags |= SIM_STATUS_FLAG_BOOT;
            } else
              LOG_WARN_ ("ping error %d\n", errno);
            if (++pinged > MAIN_DHT_BUCKET) {
              main_cpu_send += limit_set_cpu (cpu, sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL));
              main_dht_wait_ (MAIN_CALC_DELAY (count, MAIN_DHT_BUCKET, 4));
              if (main_dht_running_flag != true)
                return hosts;
              cpu = sim_system_cpu_get (SYSTEM_CPU_TIME_CYCLES, NULL);
              pinged = 0;
            }
          }
          array_free (hosts);
          hosts = array_new_strings (0);
          LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
        }
      }
    } else
      nodecount = count;
  }
  return hosts;
}

static int main_dht_save (simtype mainline, simbool savenodes) {
  int err = SIM_OK, i, count = 0, newnodes = 0;
  if (savenodes) {
    simbyte *id;
    struct sockaddr_in *sin;
    simtype nodes, oldnodes = table_get_table (mainline, FILE_KEY_MAIN_NODES);
    dht_nodes (AF_INET, &count, NULL, NULL, NULL);
    id = sim_new (MAIN_DHT_SIZE_ID * count);
    dht_get_nodes_id (sin = sim_new (sizeof (*sin) * count), id, &count, NULL, NULL, &i);
    nodes = table_new (count);
    for (i = 0; i < count; i++) {
      struct _network_ip_port node;
      node.ip = sin[i].sin_addr.s_addr, node.port = sin[i].sin_port;
      if (! dht_blacklisted ((struct sockaddr *) &sin[i], sizeof (sin[i]))) {
        simtype key = pointer_new_len (&node, CONTACT_SIZE_IP_PORT);
        simtype oldid, newid = string_copy_len (id + i * MAIN_DHT_SIZE_ID, MAIN_DHT_SIZE_ID);
        if ((oldid = table_add_key (nodes, string_copy_len (&node, CONTACT_SIZE_IP_PORT), newid)).typ != SIMNIL)
          LOG_DEBUG_SIMTYPES_ (oldid, pointer_new_len (id + i * MAIN_DHT_SIZE_ID, MAIN_DHT_SIZE_ID), LOG_BIT_BIN,
                               "duplicate %s:%d ", network_convert_ip (ntohl (node.ip)), ntohs (node.port));
        if (oldnodes.typ == SIMNIL || table_get_key_string (oldnodes, key).typ == SIMNIL) {
          LOG_XTRA_ ("SAVE %s:%d\n", network_convert_ip (ntohl (node.ip)), ntohs (node.port));
          ++newnodes;
        }
      }
    }
    sim_free (sin, sizeof (*sin) * count);
    sim_free (id, MAIN_DHT_SIZE_ID * count);
    if (newnodes || oldnodes.typ == SIMNIL) {
      simtype key, val;
      simtype oldtorrents = table_get_table (mainline, FILE_KEY_MAIN_TORRENTS);
      simtype newtorrents = table_new (table_count (oldtorrents));
      simwalker ctx;
      LOG_DEBUG_ ("save %d nodes (%d new nodes)\n", table_count (nodes), newnodes);
      count = 0;
      if (oldtorrents.typ != SIMNIL && table_walk_first (&ctx, oldtorrents))
        while ((val = table_walk_next_number (&ctx, &key)).typ != SIMNIL) {
          if (val.num) {
            table_add_key (newtorrents, string_copy_string (key), val);
          } else
            ++count;
        }
      if (count)
        LOG_DEBUG_ ("removed %d torrent nodes\n", count);
      table_set (mainline, FILE_KEY_MAIN_NODES, nodes);
      table_set (mainline, FILE_KEY_MAIN_TORRENTS, newtorrents);
    } else {
      table_free (nodes);
      LOG_DEBUG_ ("skip save (%d old nodes)\n", count);
    }
  } else
    LOG_DEBUG_ ("skip save\n");
  if (mainline.typ != SIMNIL) {
    unsigned j;
    for (j = 0; j <= CONTACT_STAT_COUNT; j++) {
      simnumber n = main_get_stat (CONTACT_STATS_CLIENTS, j) + main_get_stat (CONTACT_STATS_CLIENT, j);
      table_set_number (mainline, contact_stat_names[CONTACT_STATS_CLIENTS][j], n);
    }
    table_set (mainline, FILE_KEY_MAIN_BLACKLIST, main_get_blacklist (true));
    err = file_save (mainline, FILE_DHT, FILE_TYPE_ENCRYPTED);
    table_delete (mainline, FILE_KEY_MAIN_BLACKLIST);
  }
  return err;
}

int main_dht_load (simtype *mainline) {
  int err;
  LOG_DEBUG_ ("load %s\n", FILE_DHT);
  if ((err = file_load (FILE_DHT, FILE_TYPE_ENCRYPTED, mainline)) == SIM_OK) {
    simtype nodes = table_get_table (*mainline, FILE_KEY_MAIN_NODES);
    simtype blacklist = table_get_array_string (*mainline, FILE_KEY_MAIN_BLACKLIST);
    if (nodes.typ != SIMNIL)
      LOG_DEBUG_ ("loaded %d nodes, %d blacklisted\n", table_count (nodes), blacklist.len / 2);
  } else
    table_free (main_file);
  return err;
}

void main_dht_logon (void) {
  main_dht_announce_tick = system_get_tick ();
  main_dht_mode = MAIN_MODE_ACTIVE;
}

static void *thread_dht_ (void *arg) {
  LOG_API_DEBUG_ ("\n");
  log_get_error_ ();
  while (main_dht_running_flag == true) {
    int status, oldstatus = MAIN_DHT_DISCONNECT, count = 0, good, dubious, incoming, nodes = 0;
    LOG_DEBUG_ ("BOOTING\n");
    main_dht_disconnected_flag = true;
    array_free (main_dht_boot_ (table_get_table (main_file, FILE_KEY_MAIN_NODES)));
    main_dht_reinit_flag = false;
    if (main_dht_running_flag != true)
      break;
    event_test_error (NULL, SIM_EVENT_ERROR_FILE_SAVE, main_dht_save (main_file, true));
    network_periodic (NETWORK_CMD_START);
    do {
      status = main_get_nodes (&good, &dubious, &incoming, NULL, NULL);
      main_dht_size = good + dubious;
      if (status == MAIN_DHT_DISCONNECT) {
        if (oldstatus > MAIN_DHT_DISCONNECT)
          main_dht_logon ();
        if (main_dht_size != nodes)
          oldstatus = MAIN_DHT_CONNECTED; /* do log DISCONNECT if number of nodes has changed during boot */
      }
      event_test_net_dht (status);
      if (status != MAIN_DHT_LISTENING) {
        if (simself.flags & SIM_STATUS_FLAG_DHT_IN)
          simself.flags &= ~SIM_STATUS_FLAG_UDP_IN;
        simself.flags &= ~SIM_STATUS_FLAG_DHT_IN;
        LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
      }
      if (status != oldstatus) {
        switch (status) {
          case MAIN_DHT_DISCONNECT:
            nodes = main_dht_size;
            LOG_NOTE_ ("DISCONNECTED (%d/%d nodes)\n", good, nodes);
            if (! main_dht_reinit_flag)
              event_test_net_status (-2);
            break;
          case MAIN_DHT_CONNECTED:
            LOG_NOTE_ ("%s (%d/%d nodes, %d incoming)\n",
                       oldstatus == MAIN_DHT_LISTENING ? "NOT-LISTENING" : "CONNECTED", good, main_dht_size, incoming);
            if (oldstatus == MAIN_DHT_LISTENING)
              break;
            proxy_announce (system_get_tick (), true);
          case MAIN_DHT_LISTENING:
            if (! main_down_tick)
              event_test_net_status (2);
            if (status == MAIN_DHT_LISTENING) {
              simself.flags |= SIM_STATUS_FLAG_DHT_IN | SIM_STATUS_FLAG_UDP_IN;
              LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
              main_dht_listening_nodes = incoming;
              LOG_NOTE_ ("LISTENING (%d/%d nodes, %d incoming)\n", good, main_dht_size, incoming);
            }
            break;
          case MAIN_DHT_CONNECTING:
            LOG_NOTE_ ("%s (%d/%d nodes)\n",
                       oldstatus >= MAIN_DHT_CONNECTED ? "DISCONNECTING" : "CONNECTING", good, main_dht_size);
            main_dht_logon ();
        }
      } else if (status >= MAIN_DHT_CONNECTED && ! main_down_tick && ! main_dht_state) {
        event_test_net_status (1);
      } else if (status == MAIN_DHT_LISTENING && incoming > main_dht_listening_nodes) {
        main_dht_listening_nodes = incoming;
        LOG_DEBUG_ ("LISTENING (%d/%d nodes, %d incoming)\n", good, main_dht_size, incoming);
      }
      oldstatus = status;
      if (++count > param_get_number ("contact.save")) {
        int err = main_dht_save (main_file, status >= MAIN_DHT_CONNECTED && ! main_down_tick);
        event_test_error (NULL, SIM_EVENT_ERROR_FILE_SAVE, err);
        count = 0;
      }
      main_dht_sleep_ ();
    } while (! MAIN_CHECK_DISCONNECTED (good, dubious) && main_dht_running_flag == true);
  }
  main_dht_size = 0;
  return pth_thread_exit_ (true);
}

void main_reinit (void) {
  proxy_init_stat (-1);
  main_reset_blacklisting ();
  if (main_dht_running_flag == true && socket_udp_sock.fd != INVALID_SOCKET) {
    simtype key, searches = table_copy (main_search_table, main_search_table.len);
    simwalker ctx;
    simbyte *id;
    struct sockaddr_in *sin;
    int i, count = 0;
    for (table_walk_first (&ctx, searches); table_walk_next_table (&ctx, &key).typ != SIMNIL;) {
      struct main_search *search = MAIN_GET_SEARCH (table_get_key_table (main_search_table, key));
      search->announce = 0;
      search->restart = MAIN_RESTART_BLACKLISTED; /* prevent timer doubling in main_search_callback */
      if (! dht_end_search (key.str, AF_INET, main_search_callback, NULL))
        table_delete_key (main_search_table, key);
    }
    dht_nodes (AF_INET, &count, NULL, NULL, NULL);
    LOG_DEBUG_ ("REINIT (%d nodes, %d searches)\n", count, table_count (searches));
    table_free (searches);
    id = sim_new (count * MAIN_DHT_SIZE_ID);
    dht_get_nodes_id (sin = sim_new (sizeof (*sin) * count), id, &count, NULL, NULL, &i);
    dht_uninit ();
    random_get (random_public, pointer_new_len (main_dht_id, sizeof (main_dht_id)));
    if (dht_init (socket_udp_sock.fd, INVALID_SOCKET, main_dht_id, NULL) >= 0) {
      for (i = 0; i < count; i++)
        if (! dht_blacklisted ((struct sockaddr *) &sin[i], sizeof (sin[i])))
          dht_insert_node (id + i * MAIN_DHT_SIZE_ID, (struct sockaddr *) &sin[i], sizeof (sin[i]));
    } else {
      LOG_ERROR_ ("reinit error %d\n", errno);
      main_dht_running_flag = -1;
    }
    sim_free (sin, sizeof (*sin) * count);
    sim_free (id, count * MAIN_DHT_SIZE_ID);
    main_dht_reinit_flag = true;
  }
}

static void main_init_blacklisted (simtype mainline) {
  main_cpu_burst = param_get_number ("main.cpu.burst") * 1000;
  if (mainline.typ != SIMNIL) {
    unsigned i = 0;
    simtype blacklist = table_detach_array_string (mainline, FILE_KEY_MAIN_BLACKLIST);
    main_blacklist_index = 0;
    main_blacklist_size = param_get_number ("main.blacklisted");
    main_blacklist_buffer = sim_new (sizeof (*main_blacklist_buffer) * main_blacklist_size);
    main_blacklist_table = table_new_rand (main_blacklist_size / 2);
    memset (main_blacklist_buffer, 0, sizeof (*main_blacklist_buffer) * main_blacklist_size);
    if (param_get_number ("main.blacklist") > 0)
      while (++i < blacklist.len) {
        static const simbyte zero[sizeof (simnumber)] = { 0, 0, 0, 0, 0, 0, 0, 0 };
        unsigned *ip = blacklist.arr[i].ptr;
        if (blacklist.arr[i++].len == sizeof (*ip))
          if (! main_blacklist (ntohl (*ip), blacklist.arr[i].len >= sizeof (zero) ? blacklist.arr[i].str : zero))
            LOG_DEBUG_SIMTYPE_ (blacklist.arr[i], LOG_BIT_BIN, "duplicate %s ", network_convert_ip (ntohl (*ip)));
      }
    array_free (blacklist);
    main_cpu_table = table_new_rand (main_cpu_burst / 25);
    main_cpu_error = SIM_LIMIT_DHT_ENABLE;
  } else {
    table_free (main_cpu_table), main_cpu_table = nil ();
    table_free (main_blacklist_table), main_blacklist_table = nil ();
    sim_free (main_blacklist_buffer, sizeof (*main_blacklist_buffer) * main_blacklist_size);
    main_blacklist_buffer = NULL;
  }
  main_cpu_send = main_cpu_recv = main_cpu_count = main_blacklist_nodes = 0;
}

int main_init (void) {
  int err, i;
  main_dht_port = abs (param_get_number ("main.port"));
  LOG_DEBUG_ ("INIT port %d\n", main_dht_port);
  if (main_dht_running_flag != false || ! contact_list.me)
    return SIM_MAIN_INIT;
  if ((err = main_dht_load (&main_file)) != SIM_OK)
    return err;
  file_load_torrents (main_file);
  simself.ipdht = table_new (131);
  main_proxy_contact.nick = pointer_new (main_proxy_contact.addr = PROXY_ADDRESS_PROXY);
  if (! main_cpu_tick) {
    main_cputime_init ();
    for (i = 0; i <= CONTACT_STAT_COUNT; i++) {
      main_stats[CONTACT_STATS_CLIENTS][i] = table_get_number (main_file, contact_stat_names[CONTACT_STATS_CLIENTS][i]);
      main_stats[CONTACT_STATS_CLIENT][i] = 0;
    }
  }
  main_init_stat (true);
  sim_network_parse_ip_default (param_get_pointer ("main.ip"), &main_dht_ip);
  LOG_INFO_ ("default interface %s\n", network_convert_ip (main_dht_ip));
  simself.flags &= ~(SIM_STATUS_FLAG_DHT_OUT | SIM_STATUS_FLAG_DHT_IN | SIM_STATUS_FLAG_UDP_IN);
  LOG_CODE_DEBUG_ (network_log_flags (SIM_MODULE, SIM_LOG_DEBUG));
  main_dht_state = -1;
  main_dht_listening_threshold = main_dht_listening_value = param_get_number ("main.dht.listening");
  main_dht_reinit_flag = main_ssl_reinit_flag = main_send_error_flag = main_dht_warn_flag = false;
  main_down_flag = main_send_ok_flag = true;
  main_down_tick = main_dht_recv_tick = 0;
  main_set_tick (false);
  main_dht_logon ();
  random_get (random_public, pointer_new_len (main_dht_id, sizeof (main_dht_id)));
  if (dht_init (socket_udp_sock.fd, INVALID_SOCKET, main_dht_id, NULL) >= 0) {
    main_search_table = table_new (1021);
    if ((err = pth_thread_spawn (thread_dht_, NULL, &tid_dht, -1)) == SIM_OK) {
      main_init_blacklisted (main_file);
      main_dht_running_flag = true;
      return SIM_OK;
    }
    table_free (main_search_table), main_search_table = nil ();
    dht_uninit ();
  } else {
    err = errno ? errno : EPERM;
    LOG_ERROR_ ("init error %d\n", errno);
  }
  table_free (simself.ipdht), simself.ipdht = nil ();
  table_free (main_file), main_file = nil ();
  return err;
}

int main_exit (void) {
  int err = SIM_OK;
  LOG_DEBUG_ ("EXIT\n");
  if (main_dht_running_flag == true) {
    event_test_net_status (-3);
    main_init_stat (false);
    err = main_dht_save (main_file, main_get_status () >= MAIN_DHT_CONNECTED);
    LOG_CODE_NOTE_ (main_log_dht (SIM_MODULE, SIM_LOG_NOTE));
    main_dht_running_flag = -1;
  }
  return err;
}

int main_uninit_ (simbool init) {
  int err = SIM_MAIN_INIT;
  LOG_DEBUG_ ("UNINIT\n");
  if (main_dht_running_flag == -1) {
    err = pth_thread_join_ (&tid_dht, NULL, thread_dht_, -1);
    LOG_NOTE_ ("DISCONNECT\n");
    dht_uninit ();
    main_set_tick (main_dht_running_flag = false);
    main_init_blacklisted (nil ());
    table_free (main_search_table), main_search_table = nil ();
    table_free (main_file), main_file = nil ();
    table_free (simself.ipdht), simself.ipdht = nil ();
    event_test_net_dht (MAIN_DHT_STOPPED);
  }
  if (init)
    main_cpu_tick = 0;
  return err;
}

void main_log_searches (const char *module, int level) {
  static const char *main_state_names[] = { "STOPPED", "DISCONNECTED", "CONNECTING", "CONNECTED", "LISTENING" };
  simtype key, val;
  simwalker ctx;
  int good, dubious, incoming, blocked, blacklisted;
  int status = main_get_nodes (&good, &dubious, &incoming, &blocked, &blacklisted);
  log_any_ (module, level, "DHT is %s (%s, %d/%d nodes, %d incoming, %d blocked, %d/%d blacklisted)\n",
            main_dht_state > 0 ? "UP" : "DOWN", main_state_names[status - MAIN_DHT_STOPPED],
            good, good + dubious, incoming, blocked, main_blacklist_nodes, blacklisted);
  log_any_ (module, level, "IP %s PORT %d (%d searches)", network_convert_ip (main_dht_ip), main_dht_port,
            main_search_table.tbl ? table_count (main_search_table) : 0);
  if (simself.ipdht.tbl && table_count (simself.ipdht) && table_walk_first (&ctx, simself.ipdht))
    while ((val = table_walk_next_number (&ctx, &key)).typ != SIMNIL)
      log_any_ (module, level, " %s(%lld)", network_convert_ip (*(unsigned *) key.ptr), val.num);
  log_any_ (module, level, "\n");
  if (main_search_table.tbl && table_walk_first (&ctx, main_search_table))
    while ((val = table_walk_next_table (&ctx, &key)).typ != SIMNIL) {
      struct main_search *search = MAIN_GET_SEARCH (val);
      simcontact contact = &main_proxy_contact;
      simnumber tick;
      if (strcmp (search->addr, PROXY_ADDRESS_PROXY))
        contact = contact_list_find_address (search->addr);
      log_any_ (module, level, "%c%u %s ab=%d.%d",
                main_search_check_blacklisting (key, contact) ? '-' : search->announce ? '+' : ' ',
                search->sequence, CONTACT_GET_NICK (search->addr), search->alpha, search->beta);
      log_simtype_ (module, level, key, LOG_BIT_CONT | LOG_BIT_BIN, " ");
      log_any_ (module, level, "%s", MAIN_LOOKUP_RESTART_NAME (search));
      if (contact && (tick = contact->dht.tick) != 0)
        log_any_ (module, level, " (%lld/%d s)", (system_get_tick () - tick) / 1000, contact->dht.timeout / 1000);
      if (contact && contact->dht.count)
        log_any_ (module, level, " *%d", contact->dht.count);
      log_any_ (module, level, "\n");
    }
  if (limit_cpu[LIMIT_CPU_RATE]) {
    simunsigned rcvusec = main_cpu_recv * 1000 / limit_cpu[LIMIT_CPU_RATE];
    simunsigned sndusec = main_cpu_send * 1000 / limit_cpu[LIMIT_CPU_RATE];
    simunsigned msec = system_get_tick () - main_cpu_tick, load = msec ? 100 * (rcvusec + sndusec) / msec : 0;
    log_any_ (module, level, "CPU %lld.%03lld%% = (%lld.%03lld + %lld.%03lld) / %lld ms\n",
              load / 1000, load % 1000, rcvusec / 1000, rcvusec % 1000, sndusec / 1000, sndusec % 1000, msec);
  }
  if (main_dht_verify_tick)
    log_any_ (module, level, "VERIFY = %lld seconds\n", (simnumber) (system_get_tick () - main_dht_verify_tick) / 1000);
}

void main_log_dht (const char *module, int level) {
  simtype blacklist = param_get_number ("main.blacklist") > 0 ? main_get_blacklist (false) : nil (), key, val;
  unsigned i = 0;
  if (! module)
    dht_dump_tables (NULL);
  while (++i < blacklist.len) {
    unsigned *ip = blacklist.arr[i++].ptr;
    log_any_ (module, level, "Blacklisted id %08llx... %s\n",
              *(simnumber *) blacklist.arr[i].ptr, network_convert_ip (ntohl (*ip)));
  }
  array_free (blacklist);
  if (! module && main_cpu_table.ptr) {
    simwalker ctx;
    log_any_ (module, level, "\n");
    for (table_walk_first (&ctx, main_cpu_table); (val = table_walk_next_number (&ctx, &key)).typ != SIMNIL;)
      if (val.num & 1)
        log_any_ (module, level, "Blocked %s\n", network_convert_ip (*(unsigned *) key.ptr));
    if (param_get_number ("main.cpu.burst") * 1000 != (int) main_cpu_burst) {
      log_any_ (module, level, "\n");
      log_any_ (module, level, "BURST = %d ms\n", main_cpu_burst);
    }
  }
}
