/**
    utility functions, including sending of events and unicode conversion

    Copyright (c) 2020-2023 The Creators of Simphone

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

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600 /* localtime_r; strerror_r does not return char *, stores message in buffer */
#endif

#include "config.h"
#if ! HAVE_LIBPTH
#include <npth.h>
#endif
#include "spth.h"

#if HAVE_LIBEFENCE && defined(_WIN32)
#include "../efence/efence.h"
#endif

#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 "contact.h"
#include "proto.h"
#include "server.h"
#include "audio.h"
#include "api.h"

#if HAVE_MALLOC_USABLE_SIZE || defined(_WIN32)
#if HAVE_MALLOC_NP_H
#include <malloc_np.h>
#else
#include <malloc.h>
#endif
#endif

#ifndef _WIN32
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <locale.h>

#if HAVE_MTRACE
#include <mcheck.h>
#endif
#else
#include <io.h>
void _exit (int); /* maybe mingw doesn't have it defined */
#endif

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

#if HAVE_LIBSPEEX
#include <portaudio.h>
#endif

#if HAVE_LIBCARES
#include <ares.h>
#endif

#if HAVE_LIBEXPAT
#include <expat.h>
#endif

#ifndef STDERR_FILENO
#define STDERR_FILENO 2 /* _WIN32 */
#endif

#include <openssl/err.h>

#define SIM_MODULE "utils"

static const int convert_utf_lengths[256] =
  /* table rejects characters longer than 21 bits (can be enabled by changing zeros at the end of the table to valid lengths) */
  { 256, 256, 256, 256, 256, 256, 256, 256, 'b', 't', 'n', 256, 'f', 'r', 256, 256,
    256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
    1, 1, '"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, '/', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, '\\', 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
static const int convert_utf_masks[6] = { 0xFF, 0x3F, 0x1F, 0x0F, 0x07, 0x03 };

static const struct {
  int code;
  const char *name;
} error_codes[] = {
  { SIM_OK, "error number " },
  { SIM_CRYPT_BAD_CIPHER, "Unknown cipher" },
  { SIM_CRYPT_BAD_RSA_KEY, "Invalid RSA public key" },
  { SIM_CRYPT_NO_RSA_DECRYPT, "RSA decryption error" },
  { SIM_CRYPT_BAD_PACKET, "Packet could not be authenticated" },
  { SIM_CRYPT_BAD_TABLE, "Authenticated packet is corrupted" },
  { SIM_CRYPT_BAD_KEY, "Falsified public key" },
  { SIM_CRYPT_RSA_VERIFY, "Failed key revocation attempt" },
  { SIM_CRYPT_BAD_HANDSHAKE, "Unknown handshake type" },
  { SIM_CRYPT_NO_CIPHERS, "You have to choose at least one preferred cipher" },
  { SIM_CRYPT_NO_RANDOM, "No system random generator available" },
  { SIM_SSL_ERROR, "SSL error" },
  { SIM_SSL_NO_PREMASTER, "Premaster key generation failed" },
  { SIM_SSL_NO_MASTER, "Master key generation failed" },
  { SIM_SSL_NO_HANDSHAKE, "Handshake did not complete" },
  { SIM_SSL_BAD_TMP_KEY, "Invalid ephemeral EC key" },
  { SIM_KEY_NO_KEY, "No key available" },
  { SIM_KEY_BAD_TYPE, "Key type not supported by this version" },
  { SIM_KEY_NO_SAVE, "Cannot set password at a public computer" },
  { SIM_KEY_NO_PRIVATE, "Private key is missing" },
  { SIM_KEY_TOO_LARGE, "Key too big for use with this system" },
  { SIM_KEY_TOO_SLOW, "System is too busy to use this key" },
  { SIM_KEY_NOT_SECURE, "Your private key has been revoked" },
  { SIM_KEY_NO_ENTROPY, "Not enough entropy collected" },
  { SIM_KEY_EXISTS, "A different private key file already exists" },
  { SIM_KEY_NO_CONTACT, "The user directory needs a different secret key" },
  { SIM_KEY_NO_SAVED, "No saved key available" },
  { SIM_KEY_BAD_KEY, "Your private key is not secure" },
  { SIM_SOCKET_NO_ERROR, "Proxy socket error" },
  { SIM_SOCKET_BAD_PACKET, "Received packet is corrupted" },
  { SIM_SOCKET_BAD_LENGTH, "Tried to send an invalid packet" },
  { SIM_SOCKET_END, "Connection closed by peer" },
  { SIM_SOCKET_CANCELLED, "Connection was cancelled" },
  { SIM_SOCKET_BAD_PORT, "Invalid port number" },
  { SIM_SOCKET_TIMEOUT, "CONNECT has timed out" },
  { SIM_SOCKET_RECV_TIMEOUT, "RECV has timed out" },
  { SIM_SOCKET_SEND_TIMEOUT, "SEND has timed out" },
  { SIM_SOCKET_EXCEEDED, "Maximal data size exceeded" },
  { SIM_SOCKET_CONNECT, "Could not connect to socks/TOR proxy" },
  { SIM_SOCKET_ACCEPT, "Retrying blocked accept" },
  { SIM_SOCKET_BLOCKED, "Retry this operation later" },
  { SIM_SOCKET_RECV_FAILED, "Could not receive an SSL packet" },
  { SIM_SOCKET_RECV_ERROR, "RECV has timed out" },
  { SIM_FILE_LOCKED, "Program already running" },
  { SIM_FILE_ERROR, "File is corrupted" },
  { SIM_FILE_END, "File is truncated" },
  { SIM_FILE_NO_SPACE, "Not enough disk space available" },
  { SIM_FILE_BAD_KEY, "File cannot be decrypted" },
  { SIM_FILE_BAD_TABLE, "Tried to save a corrupted table" },
  { SIM_FILE_NO_KEY, "Tried to encrypt a file with no key" },
  { SIM_FILE_BAD_NAME, "Invalid file name" },
  { SIM_FILE_START, "No more messages to load" },
  { SIM_FILE_NO_SEEK, "Invalid file position" },
  { SIM_FILE_BAD_SIZE, "Invalid file size" },
  { SIM_FILE_BAD_TYPE, "Invalid file type" },
  { SIM_CONTACT_UNKNOWN, "Contact does not exist" },
  { SIM_CONTACT_BLOCKED, "Contact is not authorized" },
  { SIM_CONTACT_BAD_ID, "Contact identifier already exists" },
  { SIM_CONTACT_BAD_ADDRESS, "Invalid contact address" },
  { SIM_CONTACT_BAD_NICK, "Invalid nickname" },
  { SIM_CONTACT_NO_KEY, "Contact's public key is missing" },
  { SIM_CONTACT_OVERFLOW, "Too many contacts" },
  { SIM_CONTACT_BAD_VERIFY, "Invalid verification code" },
  { SIM_CONTACT_REVOKED, "This contact has revoked its public key" },
  { SIM_CONTACT_DROPPED, "Contact disconnected by user" },
  { SIM_CONTACT_EXISTS, "Contact already exists" },
  { SIM_PARAM_UNKNOWN_KEY, "Undefined configuration parameter" },
  { SIM_PARAM_BAD_KEY, "Invalid configuration parameter" },
  { SIM_PARAM_BAD_TYPE, "Invalid configuration value type" },
  { SIM_PARAM_BAD_VALUE, "Invalid configuration value" },
  { SIM_LIMIT_NO_ERROR, "Internal limit error" },
  { SIM_LIMIT_BLOCKED, "IP address had been temporarily blocked" },
  { SIM_LIMIT_NO_CLIENTS, "Too many clients" },
  { SIM_LIMIT_NO_BANDWIDTH, "Proxy server has no available bandwidth" },
  { SIM_LIMIT_NO_SPEED, "Proxy server has been slowed down too much" },
  { SIM_LIMIT_NO_HANDSHAKE, "Proxy cannot accept more clients to this server" },
  { SIM_LIMIT_NO_SOCKETS, "Proxy server has no available sockets" },
  { SIM_LIMIT_NO_SERVERS, "Proxy server has no available servers" },
  { SIM_LIMIT_NO_CUSTOMERS, "Proxy server has no available customers" },
  { SIM_LIMIT_NO_CONNECTIONS, "Proxy server has too many connections from this IP address" },
  { SIM_LIMIT_CPU_LOAD, "Incoming connections have been temporarily restricted" },
  { SIM_LIMIT_DHT_DISABLE, "Connection to the DHT network has been suspended" },
  { SIM_LIMIT_DHT_ENABLE, "Connection to the DHT network has been resumed" },
  { SIM_LIMIT_DHT_BLACKLIST, "Connections from the DHT network have been restricted" },
  { SIM_PROXY_LOCAL, "Proxy local customer" },
  { SIM_PROXY_NO_CUSTOMER, "Requested connection to unknown customer" },
  { SIM_PROXY_NO_CLIENTS, "Too many proxy clients" },
  { SIM_PROXY_NAT_CLOSE, "Traversal connection to proxy closed" },
  { SIM_PROXY_END_LOCAL, "Connection closed by local server" },
  { SIM_PROXY_END_PROXY, "Connection closed by proxy" },
  { SIM_PROXY_END_CLIENT, "Connection closed by proxy client" },
  { SIM_PROXY_END_SERVER, "Connection closed by proxy customer" },
  { SIM_PROXY_REVERSE, "Inverse proxy connection succeeded" },
  { SIM_PROXY_NOT_SERVING, "Proxy is not online" },
  { SIM_PROXY_NOT_NEEDED, "Incoming connection succeeded" },
  { SIM_PROXY_NOT_PREFERRED, "Switching to a preferred proxy" },
  { SIM_PROXY_OFFLINE, "Proxy server has gone offline" },
  { SIM_PROXY_BLOCKED, "Proxy server blocked by user" },
  { SIM_PROXY_BLACKLISTED, "Proxy server has been blacklisted" },
  { SIM_PROXY_DROPPED, "Proxy server disconnected by user" },
  { SIM_PROXY_RECONNECT, "Proxy customer already exists" },
  { SIM_NAT_TRAVERSE_TIMEOUT, "Traversal timeout" },
  { SIM_NAT_TRAVERSE_CANCELLED, "Traversal was cancelled" },
  { SIM_NAT_REVERSE_PROXY, "Connection reversal found a proxy" },
  { SIM_NAT_REVERSE_DENIED, "Connection reversal rejected" },
  { SIM_NAT_REVERSE_CANCELLED, "Connection reversal was cancelled" },
  { SIM_NAT_REVERSE_ABANDONED, "Connection reversal was abandoned" },
  { SIM_NAT_REVERSE_NO_PROXY, "Proxy connection not found" },
  { SIM_NAT_REVERSE_QUIT, "Connection reversal has failed" },
  { SIM_NAT_TRAVERSE_QUIT, "Traversal has failed" },
  { SIM_NAT_TRAVERSE_FAILED, "Your connection is being relayed" },
  { SIM_NAT_REVERSE_FINISHED, "Connection reversal has finished" },
  { SIM_NAT_TRAVERSE_ATTEMPT, "Traversal is being attempted" },
  { SIM_NAT_REVERSE_RETRY, "Connection reversal to another address" },
  { SIM_CLIENT_INIT, "Client already initialized" },
  { SIM_CLIENT_UNKNOWN, "Not currently connected to contact" },
  { SIM_CLIENT_NOT_FOUND, "Contact is offline" },
  { SIM_CLIENT_BAD_VERSION, "Handshake failed" },
  { SIM_CLIENT_SELF, "Connected to self" },
  { SIM_CLIENT_CANCELLED, "You are offline" },
  { SIM_CLIENT_CONNECTED, "Already connected to client" },
  { SIM_CLIENT_DROPPED, "Reconnected to contact" },
  { SIM_CLIENT_IDLE, "Disconnected due to inactivity" },
  { SIM_CLIENT_OFFLINE, "Client is offline" },
  { SIM_CLIENT_ONLINE, "IP address has been found already" },
  { SIM_CLIENT_LOCAL, "Client local customer" },
  { SIM_SERVER_INIT, "Server already/not initialized" },
  { SIM_SERVER_EXIT, "Server has closed the connection" },
  { SIM_SERVER_PORT, "Server cannot open incoming port number" },
  { SIM_SERVER_TIMEOUT, "Connection has expired" },
  { SIM_SERVER_RECONNECT, "Need to reconnect" },
  { SIM_SERVER_DROPPED, "Old client was dropped" },
  { SIM_SERVER_OFFLINE, "Server has logged you off" },
  { SIM_SERVER_CONTACT, "SSL contact request" },
  { SIM_CALL_NO_AUDIO, "No audio devices" },
  { SIM_CALL_BAD_AUDIO, "Bad audio device" },
  { SIM_CALL_TALKING, "Already talking" },
  { SIM_CALL_HANGUP, "Not talking" },
  { SIM_CALL_BAD_CODEC, "Unknown audio codec" },
  { SIM_CALL_BAD_SAMPLE, "Invalid sample rate" },
  { SIM_CALL_BAD_SOUND, "No such sound" },
  { SIM_CALL_NO_SAMPLE, "No such sample rate" },
  { SIM_CALL_INVALID_AUDIO, "Invalid audio device has been set" },
  { SIM_CALL_NO_ECHO_CANCELLER, "Echo cancellation not allowed" },
  { SIM_CALL_NO_AUDIO_INPUT, "Failed to read data from input audio device" },
  { SIM_CALL_NO_AUDIO_OUTPUT, "Failed to write data to output audio device" },
  { SIM_MSG_OVERFLOW, "Your memory has been exhausted" },
  { SIM_MSG_BAD_LENGTH, "Your message is too long and cannot be sent" },
  { SIM_MSG_BAD_CHAR, "Your message is not encoded as UTF-8 and cannot be sent" },
  { SIM_MSG_BAD_INDEX, "Invalid message index" },
  { SIM_MSG_NO_EDIT, "Will not delete partially-edited message" },
  { SIM_MSG_BAD_FILE, "Could not match message to chat history file" },
  { SIM_XFER_NO_ERROR, "File transfer has finished" },
  { SIM_XFER_PAUSED, "File transfer was paused" },
  { SIM_XFER_CANCELLED, "File transfer was cancelled" },
  { SIM_XFER_NO_RECV, "Too many file names have been sent to this contact" },
  { SIM_XFER_NO_SEND, "Maximal number of send attempts exceeded" },
  { SIM_XFER_NO_RESIZE, "File size has changed" },
  { SIM_XFER_NO_RESEND, "File contents have changed" },
  { SIM_XFER_NO_RESUME, "Invalid file offset" },
  { SIM_XFER_NO_FILE, "Tried to send a file that no longer exists" },
  { SIM_XFER_NO_CREATE, "Failed to create a file" },
  { SIM_XFER_NO_RENAME, "Could not use file name" },
  { SIM_XFER_NO_NAME, "Too many files have been sent" },
  { SIM_XFER_NO_SAVE, "No download directory has been chosen" },
  { SIM_XFER_BAD_HANDLE, "File transfer does not exist" },
  { SIM_XFER_BAD_ID, "File has been sent to a different computer" },
  { SIM_XFER_BAD_SIZE, "This file is too large to be received" },
  { SIM_API_INIT, "Inappropriate initialization state" },
  { SIM_API_EXIT, "You have been terminated" },
  { SIM_API_INSTALL_CANCELLED, "You have refused to enter the administrator password" },
  { SIM_API_INSTALL_FAILED, "Installation error" },
  { SIM_API_NO_RIGHT, "Disallowed communication mode" },
  { SIM_API_BAD_VERSION, "Invalid library version" },
  { SIM_API_BAD_STATUS, "Invalid status" },
  { SIM_API_BAD_SEED, "Wrong secret key or password" },
  { SIM_API_BAD_TYPE, "Invalid type or number of bits" },
  { SIM_API_BAD_PARAM, "Invalid parameter" },
  { SIM_MAIN_INIT, "DHT already/not initialized" },
  { SIM_CONSOLE_QUIT_CMD, "Console quit command" },
  { SIM_CONSOLE_NO_PARAM, "Command parameter missing" },
  { SIM_CONSOLE_BAD_CMD, "Unknown console command" },
  { SIM_CONSOLE_BAD_PARAM, "Invalid command parameter" },
  { SIM_CONSOLE_BAD_VALUE, "Invalid parameter value" },
  { SIM_NO_ERROR, "Unknown error" }
};

static char error_buffer_texts[3][SIM_SIZE_ERROR]; /* need to avoid malloc in case i am out of memory */
static pth_key_t error_buffer_keys[2] = { (pth_key_t) -1, (pth_key_t) -1 };

static int error_language = 0, error_default_language = 0;
static simtype error_table; /* keyed by error code, value is error message */

static simtype event_fatal_table;    /* preallocated SIM_EVENT_FATAL */
static simtype event_handlers_table; /* keyed by event name, value is pointer to event handler callback */

static struct thread_info {
  struct thread_info *next; /* next thread in linked list - must be first element of structure */
  pth_t tid;                /* thread identifier */
  simunsigned created;      /* thread create tick */
  simnumber number;         /* thread number */
  simtype name;             /* thread name */
  int count;                /* number of references */
} *thread_list = NULL;      /* thread list */

static simtype thread_table; /* keyed by tid, value is struct thread_info */

#define THREAD_FORMAT_NUMBERS(number) \
  (number) < 0 && (number) >= -9 ? "-" : "", (number) < 0 && (number) >= -9 ? -(number) : (number)

#ifdef _WIN32
#define pthread_t int
#define pthread_mutex_t CRITICAL_SECTION
#define pthread_mutex_init ! InitializeCriticalSectionAndSpinCount
#define pthread_mutex_lock EnterCriticalSection
#define pthread_mutex_unlock LeaveCriticalSection
#ifdef SIM_BOUNDS_CHECK
static int pthread_self () { return GetCurrentThreadId (); }
#endif
#endif

#ifdef SIM_BOUNDS_CHECK
int __bounds_checking_on = 0;
extern void (*__bounds_mutex_start) (void);
extern void (*__bounds_mutex_end) (void);
extern pthread_t (*__bounds_function_thread) (void);
#endif

#if defined(HAVE_LIBPTH) || defined(SIM_MEMORY_CHECK)
static unsigned memory_block_count = 0, memory_block_bytes = 0, memory_block_sizes = 0;
#endif

#if ! HAVE_LIBPTH && defined(HAVE_LIBPTH) || defined(SIM_BOUNDS_CHECK)
static pthread_mutex_t memory_lock;

static void sim_callback_protect (void) {
  pthread_mutex_lock (&memory_lock);
}

static void sim_callback_unprotect (void) {
  pthread_mutex_unlock (&memory_lock);
}
#elif defined(HAVE_LIBPTH) || defined(SIM_MEMORY_CHECK)
#define sim_callback_protect()
#define sim_callback_unprotect()
#endif

int sim_init_memory (void) {
  int err = SIM_OK;
#if HAVE_MTRACE && HAVE_LIBPTH
  mtrace ();
#endif
#if defined(SIM_BOUNDS_CHECK) && ! defined(_WIN32)
  pthread_mutexattr_t attr;
  if ((err = pthread_mutexattr_init (&attr)) == 0)
    if ((err = pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE)) == 0)
      err = pthread_mutex_init (&memory_lock, &attr);
#elif ! HAVE_LIBPTH && defined(HAVE_LIBPTH) || defined(SIM_BOUNDS_CHECK)
  err = pthread_mutex_init (&memory_lock, 0);
#endif
#ifdef SIM_BOUNDS_CHECK
  __bounds_mutex_start = sim_callback_protect;
  __bounds_mutex_end = sim_callback_unprotect;
  __bounds_function_thread = pthread_self;
#endif
  return err;
}

#ifdef SIM_MEMORY_CHECK
static _simblock *memory_block_list = NULL;

#define MEMORY_ADD_BLOCK(line, blocks) (memory_block_count += (blocks), (int) (line) != -SIMPOINTER)
#define MEMORY_CALC_WEIGHT(pointer) (16777619 * (unsigned long) (pointer))

static _simblock **memory_find_block (const _simblock *memory) {
  _simblock **mem = &memory_block_list;
  unsigned long weight = MEMORY_CALC_WEIGHT (memory);
  while (*mem && *mem != memory)
    mem = MEMORY_CALC_WEIGHT (*mem) < weight ? &(*mem)->right : &(*mem)->left;
  return mem;
}

static void memory_log_block (const char *module, int level, _simblock *memory) {
  unsigned bit = 0, line;
  simtype val;
#if HAVE_LIBEFENCE || ! defined(_WIN32)
  unsigned len = memory->length;
#else
  unsigned len = _msize (memory) - sizeof (_simblock);
#endif
  val.typ = -memory->line;
  val.len = len;
  val.ptr = (char *) memory + sizeof (_simblock);
  if (val.typ <= 0 && module) {
    val.typ = SIMSTRING;
    line = memory->line;
    bit = LOG_BIT_BIN;
  } else if (val.typ == SIMTABLE) {
    val.len = len / sizeof (*val.tbl) - 1;
    line = table_get_line (val);
  } else if (val.typ == SIMARRAY_ARRAY && module) {
    val.len = len / sizeof (*val.arr) - 1;
    line = array_get_line (val);
  } else {
    if (val.typ == SIMPOINTER && module)
      log_simtype_ (module, level, val, LOG_BIT_BIN, "%p (%u bytes, %d seconds) %p = %lu ", memory->file,
                    len, (int) (time (NULL) - memory->time), memory, MEMORY_CALC_WEIGHT (memory));
    return;
  }
  log_simtype_ (module, level, val, bit, "%s:%d (%u bytes, %d seconds) %p = %lu ",
                memory->file, line, len, (int) (time (NULL) - memory->time), memory, MEMORY_CALC_WEIGHT (memory));
}

static unsigned memory_log_blocks (const char *module, int level, _simblock *memory) {
  unsigned count = 0;
  do {
    count += memory->left ? memory_log_blocks (module, level, memory->left) + 1 : 1;
    memory_log_block (module, level, memory);
  } while ((memory = memory->right) != NULL);
  return count;
}

void memory_log_tables (const char *module, int level) {
  if (memory_block_list)
    log_any_ (module, level, "total %u blocks\n", memory_log_blocks (module, level, memory_block_list));
}

void *_sim_new (unsigned length, int type, const char *file, unsigned line) {
#else
#define MEMORY_ADD_BLOCK(line, blocks) (memory_block_count += (blocks), true)

void memory_log_tables (const char *module, int level) {}

void *sim_new (unsigned length) {
#endif
  void *ptr = NULL;
#if defined(HAVE_LIBPTH) || defined(SIM_MEMORY_CHECK)
  unsigned len = length;
#endif
#ifdef SIM_MEMORY_CHECK
  if (file && length)
    length += sizeof (_simblock);
#endif
  while (length && (ptr = malloc (length)) == NULL) {
    LOG_FATAL_ (SIM_OK, "MEMORY FULL = %d\n", errno);
#ifdef _WIN32
    Sleep (1000);
#else
    sleep (1);
#endif
  }
#if defined(HAVE_LIBPTH) || defined(SIM_MEMORY_CHECK)
  if (ptr) {
#ifdef SIM_MEMORY_CHECK
    if (file)
      LOG_CODE_FUNCTION_ (SIM_MODULE, SIM_LOG_XTRA, "new", file, line, "%u -> %p\n", length, ptr);
#endif
    sim_callback_protect ();
    if (MEMORY_ADD_BLOCK (line, 1))
      memory_block_bytes += len, memory_block_sizes += SIM_MEMORY_SIZE (ptr, length);
#ifdef SIM_MEMORY_CHECK
    if (file) {
      _simblock *memory = ptr;
      memory->right = memory->left = NULL;
      memory->file = file, memory->line = type == SIMNIL ? (int) line : -type;
      memory->time = (unsigned) time (NULL);
#if HAVE_LIBEFENCE || ! defined(_WIN32)
      memory->length = len;
#endif
      *memory_find_block (memory) = memory;
      ptr = (char *) ptr + sizeof (_simblock);
    }
#endif
    sim_callback_unprotect ();
  }
#endif
  return ptr;
}

void _sim_free (void *pointer, unsigned length, const char *file, unsigned line) {
  if (pointer) {
#ifdef SIM_MEMORY_CHECK
    _simblock *memory = 0, **head = NULL, **tail;
#endif
    unsigned l = length, len = l;
    if (len)
      memset (pointer, 0, len);
#ifdef SIM_MEMORY_CHECK
    if (file) {
      if (length)
        length += sizeof (_simblock);
      memory = pointer = (char *) pointer - sizeof (_simblock);
      LOG_CODE_FUNCTION_ (SIM_MODULE, SIM_LOG_XTRA, "free", file, line, "%u -> %p\n", length, pointer);
    }
#endif
#ifdef _WIN32
#if ! HAVE_LIBEFENCE
    if ((l = _msize (pointer)) != (int) length && (length || (int) line != -SIMPOINTER))
      LOG_CODE_FUNCTION_ (SIM_MODULE, SIM_LOG_ERROR, "free", file, line, "%p length %d != %d\n", pointer, length, l);
#endif
#if defined(HAVE_LIBPTH) || defined(SIM_MEMORY_CHECK)
    l = SIM_MEMORY_SIZE (pointer, length);
#endif
#else
#if HAVE_MALLOC_USABLE_SIZE && ! HAVE_LIBEFENCE
    l = SIM_MEMORY_SIZE (pointer, length);
    if ((! length && (int) line != -SIMPOINTER) || length > l)
      LOG_CODE_FUNCTION_ (SIM_MODULE, SIM_LOG_ERROR, "free", file, line, "%p length %d > %d\n", pointer, length, l);
#elif defined(HAVE_LIBPTH) || defined(SIM_MEMORY_CHECK)
    l = SIM_MEMORY_SIZE (pointer, length);
#endif
#endif /* _WIN32 */
#if defined(SIM_MEMORY_CHECK) && (HAVE_LIBEFENCE || ! defined(_WIN32))
    if (memory && memory->length != len && (len || (int) line != -SIMPOINTER))
      LOG_CODE_FUNCTION_ (SIM_MODULE, SIM_LOG_ERROR, "free", file, line, "%p length %d != %d\n",
                          pointer, len, memory->length);
#endif
#if defined(HAVE_LIBPTH) || defined(SIM_MEMORY_CHECK)
    sim_callback_protect ();
    if (MEMORY_ADD_BLOCK (line, (unsigned) -1))
      memory_block_bytes -= len, memory_block_sizes -= l;
#ifdef SIM_MEMORY_CHECK
    if (file && *(head = memory_find_block (memory)) == memory) {
      if (! (*head)->left) {
        *head = (*head)->right;
      } else if (! (*head)->right) {
        *head = (*head)->left;
      } else {
        for (tail = (_simblock **) &(*head)->left; (*tail)->right; tail = &(*tail)->right) {}
        *tail = (memory = *tail)->left;
        memory->right = (*head)->right;
        memory->left = (*head)->left;
        *head = memory;
      }
      head = NULL;
    }
#endif
    sim_callback_unprotect ();
#endif
#ifdef SIM_MEMORY_CHECK
    if (head)
      LOG_CODE_FUNCTION_ (SIM_MODULE, SIM_LOG_ERROR, "free", file, line, "zombie %p (wanted %p)\n", memory, *head);
#endif
    free (pointer);
  }
}

void _sim_free_string (char *string, const char *file, unsigned line) {
  _sim_free (string, string ? strlen (string) + 1 : 0, file, line);
}

void sim_event_send_fatal (int error, simtype line) {
  static simbool event_fatal_recursed_flag = false;
  simevent *handler = (void *) (long) table_get_number (event_handlers_table, SIM_EVENT_FATAL);
  unsigned i;
  for (i = 0; i < line.len; i++)
    if (line.str[i] >= 128)
      line.str[i] = '*';
  if (! handler || event_fatal_recursed_flag) {
#ifndef _WIN32
    if (error != SIM_NO_ERROR)
      signal (SIGPIPE, SIG_IGN);
#else
#define write _write
#endif
#if SIM_LOG_LEVEL <= SIM_LOG_FATAL
    write (STDERR_FILENO, line.str, line.len);
    write (STDERR_FILENO, "\n", sizeof (char));
#endif
    if (error != SIM_NO_ERROR)
      _exit (255);
  } else {
    event_fatal_recursed_flag = true;
    table_set_number (event_fatal_table, SIM_EVENT_FATAL, error);
    if (line.typ != SIMNIL)
      table_set (event_fatal_table, SIM_CMD_MSG_TEXT, line);
    handler (0, event_fatal_table);
    event_fatal_recursed_flag = false;
  }
}

void event_send_fast (simnumber id, const simtype event) {
  simevent *handler = (void *) (long) table_get_number (event_handlers_table, table_get_name (event));
  if (handler)
    handler (id, event);
}

void event_send_id (simnumber id, const simtype event) {
  LOG_INFO_SIMTYPES_ (pointer_new (table_get_name (event)), event, 0, "%llu ", id);
  event_send_fast (id, event);
}

void event_send_tick_type (const char *type) {
#ifndef HAVE_LIBPTH
  simtype event = table_new_name (1, SIM_EVENT_TICK);
  table_add_pointer (event, SIM_EVENT_TICK, type);
  event_send_fast (0, event);
  table_free (event);
#endif
}

void event_send_tick (simnumber id, const char *type) {
#if HAVE_LIBPTH
  simtype event = table_new_name (1, SIM_EVENT_TICK);
  table_add_pointer (event, SIM_EVENT_TICK, type);
  event_send_fast (id, event);
  table_free (event);
#endif
}

void event_send (simcontact contact, simtype event) {
  if (LOG_CHECK_LEVEL_ (SIM_LOG_INFO)) {
    simbool info = strcmp (table_get_name (event), SIM_EVENT_MSG) && strcmp (table_get_name (event), SIM_EVENT_EDIT);
    LOG_SIMTYPES_ (SIM_MODULE, info ? SIM_LOG_INFO : SIM_LOG_XTRA, pointer_new (table_get_name (event)), event, 0,
                   "'%s' ", contact ? contact->nick.str : NULL);
  }
  event_send_fast (contact ? contact->id : 0, event);
  table_free (event);
}

void event_send_audio (simcontact contact, const char *oldstate, const char *newstate) {
  simtype event = table_new_name (2, SIM_EVENT_AUDIO);
  table_add_pointer (event, SIM_EVENT_AUDIO, oldstate);
  table_add_pointer (event, CONTACT_KEY_AUDIO, newstate);
  if (contact) {
    LOG_INFO_SIMTYPE_ (event, 0, "'%s' '%s' ", SIM_EVENT_AUDIO, contact->nick.str);
  } else
    LOG_INFO_SIMTYPE_ (event, 0, "'%s' @%lld ", SIM_EVENT_AUDIO, audio_status.id);
  event_send_fast (contact ? contact->id : audio_status.id, event);
  table_free (event);
}

void event_send_name (simcontact contact, const char *name, const char *type, simtype value) {
  simtype event = table_new_name (2, name);
  table_add_pointer (event, name, type);
  if (value.typ != SIMNIL)
    table_add (event, type, value);
  event_send (contact, event);
}

void event_send_value (simcontact contact, const char *name, const char *key, simtype value) {
  simtype event = table_new_name (1, name);
  table_add (event, key, value);
  event_send (contact, event);
}

int _event_test_error_crypto (simcontact contact, const char *address, int error, const char *file, unsigned line) {
  if (error != SIM_CRYPT_BAD_TABLE && error != SIM_CRYPT_NO_RSA_DECRYPT)
    if (error != SIM_CRYPT_RSA_VERIFY && error != SIM_CRYPT_BAD_KEY)
      return error;
  if (contact) {
    const char *type = error == SIM_CRYPT_BAD_KEY ? SIM_EVENT_ERROR_KEY : SIM_EVENT_ERROR_PROTOCOL;
    event_send_name_number (contact, SIM_EVENT_ERROR, type, error);
  }
  LOG_ANY_ (error == SIM_CRYPT_BAD_KEY ? SIM_LOG_ERROR : SIM_LOG_WARN,
            "crypto %s:%u error %d '%s'\n", file, line, error, CONTACT_GET_NICK (address));
  return error;
}

int _event_test_error (simcontact contact, const char *type, int error, const char *file, unsigned line) {
  if (error != SIM_OK) {
    int oldstatus = simself.status;
    LOG_WARN_ ("file %s %s:%u error %d '%s'\n", type, file, line, error, contact ? contact->nick.str : NULL);
    if (oldstatus != SIM_STATUS_OFF) {
      simtype event = table_new_name (2, SIM_EVENT_STATUS);
      table_add_number (event, SIM_EVENT_STATUS, oldstatus);
      table_add_number (event, CONTACT_KEY_STATUS, simself.status = SIM_STATUS_OFF);
      server_logoff (SIM_CLIENT_OFFLINE, oldstatus);
      event_send_name_number (contact, SIM_EVENT_ERROR, type, error);
      event_send (NULL, event);
    }
  }
  return error;
}

void *event_register (const char *name, const void *handler) {
  return (void *) (long) table_set_number (event_handlers_table, name, (long) handler).num;
}

void event_init (void) {
  table_add_number (event_fatal_table = table_new_const_name (2, SIM_EVENT_FATAL, NULL), SIM_EVENT_FATAL, SIM_OK);
  table_add_pointer (event_fatal_table, SIM_CMD_MSG_TEXT, "");
  event_handlers_table = table_new_const (75, NULL);
  thread_table = table_new (521);
}

void event_unregister (void) {
  simtype handlers = event_handlers_table;
  event_handlers_table = table_new_const (75, NULL);
  table_free (handlers);
}

void event_uninit (void) {
  TABLE_FREE (&thread_table, nil ());
  TABLE_FREE (&event_handlers_table, nil ());
  TABLE_FREE (&event_fatal_table, nil ());
}

static void sim_convert_char (simtype *buffer, unsigned *length, int chr) {
  string_buffer_append (buffer, *length, sizeof (simbyte));
  buffer->str[(*length)++] = (simbyte) chr;
}

static unsigned sim_convert_char_to_ucs (int uch, unsigned short *ucs) {
  if (uch <= 0 || (uch >= 0xD800 && uch < 0xE000) || uch >= 0x110000)
    return 0;
  if (uch < 0x10000) {
    *ucs = (unsigned short) uch;
    return 1;
  }
  uch -= 0x10000;
  ucs[0] = (unsigned short) (0xDC00 + (uch & 0x3FF)); /* explicit little-endian; hope windows always accepts this encoding */
  ucs[1] = (unsigned short) (0xD800 + (unsigned short) (uch >> 10));
  return 2;
}

static simtype sim_convert_int_to_ucs (const int *string, unsigned *length) {
  simtype ucs;
  unsigned n = *length;
  unsigned short *s = (ucs = string_new (sizeof (*s) * (n + 3) * 2)).ptr;
  while (n--) {
    unsigned len = sim_convert_char_to_ucs (*string++, s);
    if (! len) {
      string_free (ucs);
      *length = 0;
      return nil ();
    }
    s += len;
  }
  *length = s - (unsigned short *) ucs.ptr;
  return ucs;
}

simtype sim_convert_int_to_utf (const int *string, unsigned *length) {
  static const int convert_utf_limits[6] = { 0, 0x80, 0x800, 0x10000, 0x200000, 0x4000000 };
  /* can convert ANY positive integer to UTF-8 but the allocated length is not enough for that.
     if input that outputs more than 4-byte UTF-8 sequences can ever be supplied, change the constant below from 4 to 6 */
  unsigned n = *length;
  simtype utf = string_new (n * 4);
  simbyte *s = utf.str;
  while (n--) {
    int i = -1, uch = *string++;
    while (++i < 6)
      if (uch < convert_utf_limits[i])
        break;
    if (i <= 1) {
      if (! i) {
        string_free (utf);
        *length = 0;
        return nil ();
      }
      *s++ = (simbyte) uch;
    } else {
      int k = --i * 6;
      *s++ = (simbyte) (uch >> k | ~convert_utf_masks[i]);
      while (i--) {
        k -= 6;
        *s++ = (simbyte) ((uch >> k & 0x3F) | 0x80);
      }
    }
  }
  *length = s - utf.str;
  return utf;
}

static simtype sim_convert_ucs_to_int (const unsigned short *string, unsigned *length) {
  simtype unicode;
  unsigned n = *length;
  int *s = (unicode = string_new (sizeof (*s) * n)).ptr;
  while (n--) {
    int uch = *string++;
    if (uch < 0xD800 || uch >= 0xE000) {
      *s++ = uch;
    } else if (uch >= 0xDC00) {
      if (! n || *string < 0xD800 || *string >= 0xDC00)
        break;
      n--;
      *s++ = (*string++ - 0xD800) << 10 | (uch - 0xDC00) | 0x10000;
    } else {
      if (! n || *string < 0xDC00 || *string >= 0xE000)
        break;
      n--;
      *s++ = (uch - 0xD800) << 10 | (*string++ - 0xDC00) | 0x10000;
    }
  }
  if ((int) n >= 0) {
    string_free (unicode);
    *length = 0;
    return nil ();
  }
  *length = s - (int *) unicode.ptr;
  return unicode;
}

simtype sim_convert_ucs_to_utf (const unsigned short *string, unsigned *length) {
  unsigned len = *length;
  simtype ret = nil (), unicode = sim_convert_ucs_to_int (string, length);
  if (unicode.typ == SIMNIL) {
    LOG_ERROR_SIMTYPE_ (pointer_new_len (string, len), LOG_BIT_BIN, "convert ucs-to-int failed ");
  } else if ((ret = sim_convert_int_to_utf (unicode.ptr, length)).typ == SIMNIL) {
    LOG_ERROR_SIMTYPE_ (pointer_new_len (string, len), LOG_BIT_BIN, "convert int-to-utf failed ");
  } else
    ret.str[*length] = 0;
  string_free (unicode);
  return ret;
}

static simtype _sim_convert_utf_to_int (const char *string, unsigned *length, const char *controls) {
  simtype unicode;
  int n = *length, *s = (unicode = string_new (sizeof (*s) * n)).ptr;
  while (n) {
    int len = convert_utf_lengths[(simbyte) *string], t = 0x110000;
    if (len > 8)
      len = 1;
    if (len && len <= n) {
      t = (simbyte) *string & convert_utf_masks[len - 1];
      while (--n, --len)
        t = t << 6 | (*(const simbyte *) ++string & 0x3F);
    }
    if (t >= 0x110000
#ifndef SIM_UTF_ALLOW_ALL
        || (t < ' ' && ! strchr (controls, t)) || (t >= 0xD800 && t < 0xE000) || t == 0xFFFE || t == 0xFFFF
#endif
    ) {
      string_free (unicode);
      *length = 0;
      return nil ();
    }
    *s++ = t;
    string++;
  }
  *length = s - (int *) unicode.ptr;
  return unicode;
}

simtype sim_convert_utf_to_int (const char *string, unsigned *length) {
  return _sim_convert_utf_to_int (string, length, "\t\n");
}

simtype sim_convert_utf_to_ucs (const char *string, unsigned *length) {
  simtype ret = nil (), unicode = _sim_convert_utf_to_int (string, length, "\t\r\n");
  if (unicode.typ != SIMNIL && (ret = sim_convert_int_to_ucs (unicode.ptr, length)).typ != SIMNIL)
    *((short *) ret.ptr + *length) = 0;
  string_free (unicode);
  return ret;
}

simtype sim_convert_utf (const char *string, unsigned *length) {
#ifdef _WIN32
  simtype ret = nil (), unicode = _sim_convert_utf_to_int (string, length, "");
  if (unicode.typ != SIMNIL && (ret = sim_convert_int_to_ucs (unicode.ptr, length)).typ != SIMNIL)
    *((short *) ret.ptr + *length) = 0;
  string_free (unicode);
  return ret;
#else
  return _sim_convert_utf_to_int (string, length, "");
#endif
}

static simbool sim_convert_to_json (simtype *buffer, unsigned *length, const simtype value) {
  unsigned len = value.len;
  simbyte *str;
  simtype key, val, *ptr;
  char buf[21];
  simwalker ctx;
  switch (value.typ) {
    default:
      return false;
    case SIMNUMBER:
      sprintf ((char *) (str = (simbyte *) buf), "%" SIM_FORMAT_64 "d", value.num);
      for (len = strlen (buf); len--; sim_convert_char (buffer, length, *str++)) {}
      break;
    case SIMSTRING:
    case SIMPOINTER:
      sim_convert_char (buffer, length, '"');
      str = value.ptr;
      while (len--) {
        unsigned short ucs[2];
        int c = *str++;
        unsigned l;
        switch (l = convert_utf_lengths[c]) {
          default:
            sim_convert_char (buffer, length, '\\');
            c = l;
          case 1:
            sim_convert_char (buffer, length, c);
            break;
          case 4:
            c &= 7;
          case 3:
            c &= 0x0F;
          case 2:
            c &= 0x1F;
            if (--l > len)
            case 0:
              goto quit;
            for (; l--; len--)
              c = c << 6 | (*str++ & 0x3F);
          case 256:
            if ((l = sim_convert_char_to_ucs (c, ucs)) == 0)
              goto quit;
            while (l--) { /* reverse the encoding into explicit big-endian */
              sprintf (buf, "%04X", ucs[l]);
              sim_convert_char (buffer, length, '\\');
              sim_convert_char (buffer, length, 'u');
              sim_convert_char (buffer, length, buf[0]);
              sim_convert_char (buffer, length, buf[1]);
              sim_convert_char (buffer, length, buf[2]);
              sim_convert_char (buffer, length, buf[3]);
            }
        }
      }
    quit:
      sim_convert_char (buffer, length, '"');
      break;
    case SIMTABLE:
      sim_convert_char (buffer, length, '{');
      for (table_walk_first (&ctx, value); (val = table_walk_next (&ctx, &key)).typ != SIMNIL;) {
        if (! len)
          sim_convert_char (buffer, length, ',');
        len = 0;
        if (! sim_convert_to_json (buffer, length, key))
          return false;
        sim_convert_char (buffer, length, ':');
        if (! sim_convert_to_json (buffer, length, val))
          return false;
      }
      sim_convert_char (buffer, length, '}');
      break;
    case SIMARRAY_NUMBER:
    case SIMARRAY_STRING:
    case SIMARRAY_TABLE:
    case SIMARRAY_ARRAY:
      sim_convert_char (buffer, length, '[');
      for (ptr = value.ptr; len--;) {
        if (! sim_convert_to_json (buffer, length, *++ptr))
          return false;
        if (len)
          sim_convert_char (buffer, length, ',');
      }
      sim_convert_char (buffer, length, ']');
  }
  return true;
}

static simbool sim_convert_to_xml (simtype *buffer, unsigned *length, const simtype value, const simtype tag);

#define FILE_XML_CHECK_COMMENT(tag) \
  ((tag).len >= 3 && (tag).str[0] == '!' && (tag).str[1] == '-' && (tag).str[2] == '-')

static simbool sim_convert_to_xml_start (simtype *buffer, unsigned *length, const simtype tag) {
  if (tag.len) {
    sim_convert_char (buffer, length, '<');
    if (! sim_convert_to_xml (buffer, length, tag, nil ()))
      return false;
    if (! FILE_XML_CHECK_COMMENT (tag))
      sim_convert_char (buffer, length, '>');
  }
  return true;
}

static void sim_convert_to_xml_stop (simtype *buffer, unsigned *length, const simtype tag) {
  if (tag.len) {
    if (! FILE_XML_CHECK_COMMENT (tag)) {
      sim_convert_char (buffer, length, '<');
      sim_convert_char (buffer, length, '/');
      sim_convert_to_xml (buffer, length, tag, nil ());
    } else
      sim_convert_to_xml (buffer, length, pointer_new ("--"), nil ());
    sim_convert_char (buffer, length, '>');
  }
}

static simbool sim_convert_to_xml (simtype *buffer, unsigned *length, const simtype value, const simtype tag) {
  unsigned len = value.len;
  simbyte *str = value.ptr;
  simtype key, val, *ptr;
  char buf[21];
  simwalker ctx;
  switch (value.typ) {
    default:
      return false;
    case SIMNUMBER:
      sprintf ((char *) (str = (simbyte *) buf), "%" SIM_FORMAT_64 "u", value.num);
      len = strlen (buf);
    case SIMSTRING:
    case SIMPOINTER:
      while (len--) {
        int ch = *str++;
        switch (ch) {
          case '&':
            sim_convert_char (buffer, length, '&');
            sim_convert_char (buffer, length, 'a');
            sim_convert_char (buffer, length, 'm');
            sim_convert_char (buffer, length, 'p');
            ch = ';';
          case '\t':
          case '\r':
          case '\n':
            break;
          case '<':
            sim_convert_char (buffer, length, '&');
            sim_convert_char (buffer, length, 'l');
            goto t;
          case '>':
            sim_convert_char (buffer, length, '&');
            sim_convert_char (buffer, length, 'g');
          t:
            sim_convert_char (buffer, length, 't');
            ch = ';';
          default:
            if (ch < ' ')
              ch = ' ';
        }
        sim_convert_char (buffer, length, ch);
      }
      break;
    case SIMTABLE:
      for (table_walk_first (&ctx, value); (val = table_walk_next (&ctx, &key)).typ != SIMNIL;) {
        if (! sim_convert_to_xml_start (buffer, length, key))
          return false;
        if (! sim_convert_to_xml (buffer, length, val, key))
          return false;
        sim_convert_to_xml_stop (buffer, length, key);
      }
      break;
    case SIMARRAY_NUMBER:
    case SIMARRAY_STRING:
    case SIMARRAY_TABLE:
      for (ptr = value.ptr; len--;) {
        if (! sim_convert_to_xml_start (buffer, length, tag))
          return false;
        if (! sim_convert_to_xml (buffer, length, *++ptr, nil ()))
          return false;
        sim_convert_to_xml_stop (buffer, length, tag);
      }
  }
  return true;
}

simtype sim_convert_type_to_xml (const simtype value, const char *tag) {
  unsigned len = 0;
  simtype key = nil (), buf = string_buffer_new (256);
  if (tag)
    sim_convert_to_xml_start (&buf, &len, key = pointer_new (tag));
  if (/*type_size (value) &&*/ sim_convert_to_xml (&buf, &len, value, key)) {
    if (tag)
      sim_convert_to_xml_stop (&buf, &len, key);
    return string_buffer_truncate (buf, len);
  }
  string_buffer_free (buf);
  return nil ();
}

simtype sim_convert_type_to_json (const simtype value) {
  unsigned len = 0;
  simtype buf = string_buffer_new (type_size (value));
  if (buf.ptr && sim_convert_to_json (&buf, &len, value))
    return string_buffer_truncate (buf, len);
  string_buffer_free (buf);
  return nil ();
}

simtype sim_convert_simunsigned_to_string (const char *prefix, simunsigned id, const char *suffix) {
  char b[21];
  sprintf (b, (prefix && ! *prefix) || (suffix && ! *suffix) ? "%" SIM_FORMAT_64 "u" : "%020" SIM_FORMAT_64 "u", id);
  return string_concat (prefix ? prefix : "", b, suffix, NULL);
}

unsigned sim_convert_time_to_string (simunsigned datetime, char output[SIM_SIZE_TIME]) {
  time_t tv = datetime;
#ifndef _WIN32
  struct tm res, *tm = localtime_r (&tv, &res);
#else
  struct tm *tm = localtime (&tv); /* windows uses buffer in TLS */
#define mktime(tm) ((unsigned) mktime (tm))
#endif
  if (tm && tm->tm_year >= 0 && tm->tm_year < 8100) {
    sprintf (output, "%04u-%02u-%02u %02u:%02u:%02u",
             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
  } else
    memset (output, 0, SIM_SIZE_TIME);
  return strlen (output);
}

simunsigned sim_convert_string_to_time (const char *datetime, unsigned length) {
  time_t tv = 0;
  struct tm tm;
  char buf[SIM_SIZE_TIME];
  memset (&tm, 0, sizeof (tm));
  tm.tm_isdst = -1;
  if (length >= SIM_SIZE_TIME - 1) {
    memcpy (buf, datetime, SIM_SIZE_TIME - 1);
    buf[SIM_SIZE_TIME - 1] = 0;
    datetime = buf;
    length = 0;
  }
  if (! length && sscanf (datetime, "%04d-%02d-%02d %02d:%02d:%02d",
                          &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) {
    tm.tm_mon--;
    tm.tm_year -= 1900;
    if ((int) (tv = mktime (&tm)) == -1)
      tv = 0;
  }
  return tv;
}

simtype sim_convert_string_to_locase (const char *string) {
  simtype copy = string_new (strlen (string));
  char *s = copy.ptr;
  while ((*s++ = (char) tolower (*string++)) != 0) {}
  return copy;
}

simtype sim_convert_string_to_upcase (const char *string) {
  simtype copy = string_new (strlen (string));
  char *s = copy.ptr;
  while ((*s++ = (char) toupper (*string++)) != 0) {}
  return copy;
}

simtype sim_convert_string_to_hash (const simtype string) {
  simtype addr = nil ();
  if (string.typ == SIMSTRING || string.typ == SIMPOINTER) {
    simtype hash = sim_crypt_md_hash (sim_crypt_md_new (CRYPT_MD_RIPEMD), nil (), nil (), string);
    addr = sim_contact_convert_to_address (hash, 'F');
    string_free (hash);
  }
  return addr;
}

int sim_convert_string_to_enum (const char *name, int min, int def, const char *names[], unsigned size) {
  unsigned i;
  for (i = 0; name && i < size; i++)
    if (! strcmp (names[i], name))
      return i + min;
  return def;
}

simtype sim_convert_flags_to_strings (simunsigned flags, const char *names[], unsigned size) {
  unsigned i, j;
  simtype array = array_new_strings (size);
  for (i = j = 0; i < size; i++) {
    if (flags & 1 && names[i])
      array.arr[++j] = pointer_new (names[i]);
    flags >>= 1;
  }
  array.len = j;
  return array;
}

simunsigned sim_convert_strings_to_flags (const simtype array, const char *names[], unsigned size) {
  unsigned i, j;
  simunsigned flags = 0;
  for (i = 1; i <= array.len; i++) {
    simunsigned oldflags = flags;
    for (j = 0; j < size; j++)
      if (names[j] && ! string_check_diff (array.arr[i], names[j]))
        flags |= (simunsigned) 1 << j;
    if (flags == oldflags && array_size (array) > array.len && array.arr[array.len].typ == SIMNUMBER)
      return 0;
  }
  return flags;
}

const char *convert_error (int error, simbool peek) {
  return sim_error_get_message (error, 0, peek, error_buffer_texts[0]);
}

const char *sim_error_peek (int error) {
  int lang = error_language == error_default_language ? 0 : error_language;
  return sim_error_get_message (error, lang, true, error_buffer_texts[1]);
}

const char *sim_error_get (int error) {
  int lang = error_language == error_default_language ? 0 : error_language;
  return sim_error_get_message (error, lang, false, error_buffer_texts[2]);
}

const char *sim_error_get_buffer (int error, simbool peek, char output[SIM_SIZE_ERROR]) {
  return sim_error_get_message (error, error_language == error_default_language ? 0 : error_language, peek, output);
}

const char *sim_error_get_message (int error, int language, simbool peek, char output[SIM_SIZE_ERROR]) {
  const char *text = error || language ? NULL : "OK", *info;
#ifdef __unix__
  if (error && (error < 0 || ! language) && error_table.ptr)
#else
  if (error < 0 && error_table.ptr)
#endif
    text = table_get_key_pointer (error_table, pointer_new_len (&error, sizeof (error)));
  if (error == SIM_SSL_ERROR) {
    unsigned long err = peek ? ERR_peek_error () : ERR_get_error ();
    if (err && output) {
      memset (output, 0, SIM_SIZE_ERROR);
      do { /* report the full openssl error stack */
        unsigned len = strlen (output);
        if (len < SIM_SIZE_ERROR - 3)
          strcat (strncpy (output + len, ERR_error_string (err, NULL), SIM_SIZE_ERROR - 3 - len), "\n");
      } while (! peek && (err = ERR_get_error ()) != 0);
      output[strlen (output) - 1] = 0;
      text = output;
    } else if (err && ! peek)
      while (ERR_get_error ()) {}
#if HAVE_LIBEXPAT
  } else if (error < ERROR_BASE_EXPAT) {
    text = XML_ErrorString (ERROR_BASE_EXPAT - error);
#endif
  } else if (error < ERROR_BASE_CARES) {
#if HAVE_LIBCARES
    if ((text = ares_strerror (ERROR_BASE_CARES - error)) != NULL && ! strcmp (text, "unknown"))
      text = NULL;
#endif
#if HAVE_LIBSPEEX
    if (! text && (text = Pa_GetErrorText (error)) != NULL && output) {
      const unsigned l = SIM_STRING_GET_LENGTH_CONST ("Portaudio ");
      if (! SIM_STRING_CHECK_DIFF_CONST (text, "PortAudio "))
        text += SIM_STRING_GET_LENGTH_CONST ("PortAudio ");
      text = strcat (strcpy (output, "Portaudio "), text);
      output[l] = tolower (output[l]);
    }
#endif
  }
  if (! text && output) {
#ifdef _WIN32
    unsigned l;
    if (language) {
      simtype str;
      wchar_t *buf = (wchar_t *) output;
      l = FormatMessageW (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                          NULL, error, language, buf, SIM_SIZE_ERROR / sizeof (*buf) - 1, NULL);
      str = sim_convert_ucs_to_utf (buf, &l);
      if (l >= SIM_SIZE_ERROR)
        l = 0;
      memcpy (output, str.str, l);
      string_free (str);
    } else
      l = FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ARGUMENT_ARRAY,
                         NULL, error, error_default_language, output, SIM_SIZE_ERROR - 1, NULL);
    if (l) {
      for (; l && (((simbyte *) output)[l - 1] <= ' ' || output[l - 1] == '.'); l--) {}
      output[l] = 0;
      text = output;
    }
#else
    *output = 0;
#if HAVE_STRERROR_R
    text = output;
    strerror_r (error, output, SIM_SIZE_ERROR - 1);
#elif HAVE_LIBPTH
    text = strerror (error);
#endif
#endif /* _WIN32 */
    if (! text || ! *text) {
      text = output;
      sprintf (output, "%s%d", error_codes->name, error);
    }
  }
  if (output && (info = pth_key_getdata (error_buffer_keys[0])) != NULL)
    if (pth_key_getdata (error_buffer_keys[1]) == (void *) (long) error) {
      if (text != output)
        text = strcpy (memset (output, 0, SIM_SIZE_ERROR), text);
      strncpy (output + strlen (output), info, SIM_SIZE_ERROR - strlen (output));
      output[SIM_SIZE_ERROR - 1] = 0;
    }
  if (! peek)
    sim_error_reset_text ();
  return text;
}

char *sim_error_get_text (int error) {
  char *s;
  if (pth_key_getdata (error_buffer_keys[1]) == (void *) (long) error)
    if ((s = pth_key_getdata (error_buffer_keys[0])) != NULL)
      return string_copy (s).ptr;
  return NULL;
}

int sim_error_set_text (const char *prefix, const char *text, const char *suffix, int error) {
  char *s = pth_key_getdata (error_buffer_keys[0]);
  error = pth_key_setdata (error_buffer_keys[1], (void *) (long) error) ? SIM_OK : errno;
  if (pth_key_setdata (error_buffer_keys[0], prefix ? string_concat (prefix, text, suffix, NULL).ptr : NULL)) {
    sim_free_string (s);
  } else
    error = errno;
  return error;
}

int sim_error_save_text (const char **text) {
  *text = pth_key_getdata (error_buffer_keys[0]);
  if (! pth_key_setdata (error_buffer_keys[0], NULL))
    *text = NULL;
  return (int) (long) pth_key_getdata (error_buffer_keys[1]);
}

void sim_error_load_text (const char *text, int error) {
  char *s = pth_key_getdata (error_buffer_keys[0]);
  if (pth_key_setdata (error_buffer_keys[0], text))
    sim_free_string (s);
  pth_key_setdata (error_buffer_keys[1], (void *) (long) error);
}

int sim_error_init_language (int language) {
  int lang = error_language;
  if (language < 0) {
    error_language = error_default_language;
  } else if ((error_language = language) != 0) {
#ifndef _WIN32
    const char *s = setlocale (LC_MESSAGES, NULL);
    simtype locale = s ? string_copy (s) : nil (), error = string_copy (sim_error_peek (EPERM));
    simbool ok = ! setlocale (LC_MESSAGES, "C") || string_check_diff (error, sim_error_peek (EPERM));
    if (ok && (s = setlocale (LC_MESSAGES, "")) != NULL && locale.ptr) {
      char *p = locale.ptr, *e1 = strchr (p, '_'), *e2 = strchr (s, '_');
      simtype ptr = pointer_new_len (p, e1 ? e1 - p + 1 : (int) strlen (p));
      if (string_check_diff_len (ptr, s, e2 ? e2 - s + 1 : (int) strlen (s)))
        ok = string_check_diff (error, sim_error_peek (EPERM));
    }
    setlocale (LC_MESSAGES, locale.ptr);
    string_free (error);
    string_free (locale);
#else
    simbool ok = strncmp (sim_error_peek (SIM_OK), error_codes->name, strlen (error_codes->name));
#endif
    if (! ok) {
      error_language = lang;
      lang = -1;
    }
  }
  return lang;
}

int error_init (void) {
  int err = SIM_OK;
  if (! error_table.ptr && ! pth_init ()) {
    err = errno ? errno : ENOMEM;
  } else if ((error_buffer_keys[0] == (pth_key_t) -1 && ! pth_key_create (&error_buffer_keys[0], NULL)) ||
             (error_buffer_keys[1] == (pth_key_t) -1 && ! pth_key_create (&error_buffer_keys[1], NULL))) {
    err = errno;
    pth_kill ();
    TABLE_FREE (&error_table, nil ());
  } else {
#ifdef _WIN32
    int lang = error_language;
    error_default_language = MAKELCID (MAKELANGID (LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
    if (sim_error_init_language (error_default_language) < 0)
      error_default_language = 0;
    error_language = lang;
#endif
    if (! error_table.ptr) {
      unsigned i;
#ifdef __unix__
      const char *s = setlocale (LC_MESSAGES, NULL);
      simtype locale = s ? string_copy (s) : nil ();
#endif
      error_table = table_new (101);
      for (i = 1; i < SIM_ARRAY_SIZE (error_codes); i++) {
        simtype key = pointer_new_len (&error_codes[i].code, sizeof (error_codes[i].code));
        table_add_key_pointer (error_table, key, error_codes[i].name);
      }
#ifdef __unix__
      if (locale.typ != SIMNIL && setlocale (LC_MESSAGES, "C")) {
        for (i = 0; i < 255; i++)
          if ((s = strerror (i)) != NULL && SIM_STRING_CHECK_DIFF_CONST (s, "Unknown error"))
            table_add_key (error_table, string_copy_len (&i, sizeof (i)), string_copy (s));
        setlocale (LC_MESSAGES, locale.ptr);
      }
      string_free (locale);
#endif
    }
  }
  return err;
}

int error_uninit (void) {
  int err = SIM_OK;
  sim_error_reset_text ();
  if ((error_buffer_keys[0] != (pth_key_t) -1 && ! pth_key_delete (error_buffer_keys[1])) ||
      (error_buffer_keys[1] != (pth_key_t) -1 && ! pth_key_delete (error_buffer_keys[0]))) {
    err = errno;
    LOG_ERROR_ ("pth_key_delete error %d\n", err);
  }
  error_buffer_keys[0] = error_buffer_keys[1] = (pth_key_t) -1;
  error_default_language = error_language = 0;
  if (error_table.ptr && ! pth_kill ())
    LOG_ERROR_ ("pth_kill error %d\n", errno);
  TABLE_FREE (&error_table, nil ());
  return err;
}

void *sim_list_delete (void *list, const void *element) {
  void **next;
  for (next = *(void **) list; next; next = *(void **) (list = next))
    if (next == element) {
      *(void **) list = *next;
      return list;
    }
  return NULL;
}

void sim_list_append (void *list, void *last, void *element) {
  if (last) {
    *(const void **) last = element;
  } else
    *(const void **) list = element;
  *(void **) element = NULL;
}

int _pth_queue_new (const char *module, void *msgport, void *event, int fd) {
  int err = errno = 0;
  const char *operation;
  pth_event_t *ev = event;
  pth_msgport_t *mp = msgport;
  if ((*mp = pth_msgport_create (NULL)) == NULL) {
    err = errno;
    operation = "pth_msgport_create";
    *ev = NULL;
  } else if ((*ev = pth_event (PTH_EVENT_MSG, *mp)) == NULL) {
    err = errno;
    operation = "pth_event";
    SIM_CALL_NULL (pth_msgport_destroy, mp);
  } else
    return SIM_OK;
  if (fd >= 0) {
    LOG_CODE_ANY_ (module, SIM_LOG_ERROR, "%s $%d error %d\n", operation, fd, err);
  } else
    LOG_CODE_ANY_ (module, SIM_LOG_ERROR, "%s error %d\n", operation, err);
  return err ? err : ENOMEM;
}

void pth_queue_free (void *msgport, void *event, unsigned size) {
  void *item;
  pth_event_t *ev = event;
  pth_msgport_t *mp = msgport;
  for (; size && (item = pth_msgport_get (*mp)) != NULL; sim_free (item, size)) {}
  SIM_CALL_NULL (pth_event_free, ev, PTH_FREE_THIS);
  SIM_CALL_NULL (pth_msgport_destroy, mp);
}

int _pth_queue_put (const char *module, void *msgport, void *item) {
  int err = errno = SIM_OK;
  ((pth_message_t *) item)->m_replyport = NULL;
  if (! pth_msgport_put (msgport, item)) {
    err = errno;
    LOG_CODE_ANY_ (module, msgport ? SIM_LOG_ERROR : SIM_LOG_WARN, "pth_msgport_put error %d\n", err);
  }
  return err;
}

int _pth_queue_wait_ (const char *module, void *event, int fd) {
  int ret = errno = 0;
  if ((ret = pth_wait_ (event)) <= 0) {
    if (fd >= 0) {
      LOG_CODE_ANY_ (module, SIM_LOG_ERROR, "pth_wait $%d %s %d\n", fd, ret < 0 ? "error" : "ERROR", errno);
    } else
      LOG_CODE_ANY_ (module, SIM_LOG_ERROR, "pth_wait %s %d\n", ret < 0 ? "error" : "ERROR", errno);
  }
  return ret;
}

static struct thread_info *pth_thread_new (pth_t tid, simunsigned tick, simtype name, simnumber number) {
  struct thread_info *info = sim_new (sizeof (*info));
  const int infosize = sizeof (*info);
  info->next = thread_list;
  info->tid = tid;
  info->created = tick;
  info->number = number;
  info->name = name;
  info->count = 1;
  if (table_set_key_pointer_len (thread_table, string_copy_len (&tid, sizeof (tid)), info, infosize).typ != SIMNIL)
    LOG_ERROR_ ("thread %p %s[%s%llu] zombie\n", tid, info->name.str, THREAD_FORMAT_NUMBERS (info->number));
  return thread_list = info;
}

void *_pth_thread_exit_ (simbool ssl, const char *function) {
  pth_t tid = (ssl ? (ssl_exit_thread_ (), 0) : 0, pth_self ());
  simtype th = pointer_new_len (&tid, sizeof (tid));
  int err = sim_error_reset_text ();
  if (err != SIM_OK)
    LOG_ERROR_ ("thread %s error %d\n", function, err);
  pth_thread_release (table_get_key_string (thread_table, th));
  table_delete_key (thread_table, th);
  return NULL;
}

int _pth_thread_spawn (void *(*thread) (void *), void *context, simcontact contact, void *tid,
                       simtype name, simnumber number) {
  int err = errno = SIM_OK;
  pth_t *th = tid, t = NULL;
  pth_attr_t attr;
  if ((attr = pth_attr_new ()) != NULL)
    pth_attr_set (attr, PTH_ATTR_JOINABLE, th != NULL);
  if (name.typ == SIMNIL || ! attr || (t = pth_spawn (attr, thread, context)) == NULL) {
    simtype event;
    err = errno ? errno : ENOMEM;
    table_add_pointer (event = table_new_name (2, SIM_EVENT_ERROR), SIM_EVENT_ERROR, SIM_EVENT_ERROR_INIT);
    table_add_pointer (event, SIM_EVENT_ERROR_INIT_TYPE, SIM_EVENT_ERROR_INIT_TYPE_THREAD);
    table_add_pointer_len (event, SIM_EVENT_ERROR_INIT_NAME, name.typ != SIMNIL ? name.str : (simbyte *) "", name.len);
    table_add_number (event, SIM_EVENT_ERROR_INIT, err);
    event_send (contact, event);
    LOG_WARN_ ("thread spawn %s[%s%llu] error %d '%s'\n", name.str, THREAD_FORMAT_NUMBERS (number), err,
               contact ? contact->nick.str : NULL);
  } else {
    LOG_INFO_ ("%s %s[%s%llu] %p '%s'\n", th ? "spawn" : "spawned", name.str, THREAD_FORMAT_NUMBERS (number), t,
               contact ? contact->nick.str : NULL);
    pth_thread_new (t, string_check_diff (name, "thread_pnp_") ? system_get_tick () : sim_get_tick (), name, number);
    name = nil ();
  }
  pth_attr_destroy (attr);
  if (th)
    *th = t;
  string_free (name);
  return err;
}

int _pth_thread_join_ (void *tid, void **value, const char *name, simnumber number) {
  int err = errno = SIM_OK;
  pth_t *th = tid, t = *th;
  *th = NULL;
  LOG_INFO_ ("join %s[%s%llu] %p\n", name, THREAD_FORMAT_NUMBERS (number), t);
  if (t && ! pth_join_ (t, value)) {
    err = errno;
    LOG_ERROR_ ("thread join %s[%s%llu] error %d\n", name, THREAD_FORMAT_NUMBERS (number), err);
  } else
    LOG_ANY_ (t ? SIM_LOG_INFO : SIM_LOG_WARN, "joined %s[%s%llu] %p\n", name, THREAD_FORMAT_NUMBERS (number), t);
  return err;
}

void pth_thread_set_priority (void) {
#ifdef _WIN32
  SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL);
#elif ! HAVE_LIBPTH && defined(HAVE_PTHREAD_SETSCHEDPRIO)
  int policy = 0, priority;
  pthread_attr_t attr;
  if (! pthread_attr_init (&attr)) {
    if (! pthread_attr_getschedpolicy (&attr, &policy))
      if ((priority = sched_get_priority_max (policy)) != -1)
        pthread_setschedprio (pthread_self (), priority);
    pthread_attr_destroy (&attr);
  }
#endif
}

int pth_thread_yield_ (unsigned useconds) {
#ifdef HAVE_LIBPTH
#if HAVE_LIBPTH
  if (pth_ctrl (PTH_CTRL_GETTHREADS_READY | PTH_CTRL_GETTHREADS_NEW)) {
    pth_yield (NULL);
    return 0;
  }
#endif
  pth_usleep_ (useconds);
#endif
  return pth_yield (NULL);
}

simtype pth_thread_acquire (void) {
  pth_t tid = pth_self (), null = NULL;
  struct thread_info *info = table_get_key_pointer (thread_table, pointer_new_len (&tid, sizeof (tid)));
  if (info || (info = table_get_key_pointer (thread_table, pointer_new_len (&null, sizeof (null)))) != NULL) {
    info->count++;
  } else
    info = pth_thread_new (null, system_get_tick (), nil (), -1);
  LOG_DEBUG_ ("thread %p %s[%s%llu] += %d\n", info->tid, info->name.str,
              THREAD_FORMAT_NUMBERS (info->number), info->count);
  return pointer_new_len (info, sizeof (*info));
}

simunsigned pth_thread_get_tick (void) {
  pth_t tid = pth_self ();
  struct thread_info *info = table_get_key_pointer (thread_table, pointer_new_len (&tid, sizeof (tid)));
  return info ? info->created : 0;
}

void *pth_thread_release (simtype thread) {
  struct thread_info *info = thread.ptr;
  if (! info || ! info->count) {
    LOG_ERROR_ ("zombie threads %p %s[%s%llu]\n", info ? info->tid : pth_self (),
                info ? (char *) info->name.str : "main", THREAD_FORMAT_NUMBERS (info ? info->number : -9));
  } else if (! --info->count) {
    if (info->name.typ == SIMNIL)
      table_delete_key (thread_table, pointer_new_len (&info->tid, sizeof (info->tid)));
    if (sim_list_delete (&thread_list, info)) {
      LOG_DEBUG_ ("thread %p %s[%s%llu]\n", info->tid, info->name.str, THREAD_FORMAT_NUMBERS (info->number));
      string_free (info->name);
      sim_free (info, sizeof (*info));
    } else
      LOG_ERROR_ ("zombie thread %p %s[%s%llu]\n", info->tid, info->name.str, THREAD_FORMAT_NUMBERS (info->number));
  } else
    LOG_DEBUG_ ("thread %p %s[%s%llu] -= %d\n", info->tid, info->name.str,
                THREAD_FORMAT_NUMBERS (info->number), info->count);
  return NULL;
}

simbool pth_log_threads (const char *module, int level) {
  struct thread_info *thread;
  for (thread = thread_list; thread; thread = thread->next) {
    void *tid = thread->tid ? (char *) pth_get_handle (thread->tid) + 1 : NULL;
    simnumber cpu = sim_system_cpu_get (thread->tid ? SYSTEM_CPU_TIME_THREAD : SYSTEM_CPU_TIME_PROCESS, tid);
    simnumber msec = system_get_tick () - thread->created;
    log_any_ (module, level, "%p", thread->tid);
    if (cpu) {
      log_any_ (module, level, " (%lld.%03lld/%lld.%03llds = %lld.%03lld%%)", cpu / 1000000000, cpu / 1000000 % 1000,
                msec / 1000, msec % 1000, msec ? (cpu / msec) / 10000 : 0, msec ? (cpu / msec) % 10000 / 10 : 0);
    } else
      log_any_ (module, level, " (%lld.%03llds)", msec / 1000, msec % 1000);
    log_any_ (module, level, " %s", thread->name.ptr || thread->tid ? (char *) thread->name.str : "main");
    if (thread->number >= 0 || thread->number < -9)
      log_any_ (module, level, "[%s%llu]", THREAD_FORMAT_NUMBERS (thread->number));
    socket_log_thread (module, level, thread);
    log_any_ (module, level, "\n");
  }
#if defined(HAVE_LIBPTH) || defined(SIM_MEMORY_CHECK)
  log_any_ (module, level, "memory: %u/%u bytes (%u blocks)\n",
            memory_block_bytes, memory_block_sizes, memory_block_count);
  return ! memory_block_bytes && ! memory_block_sizes && ! memory_block_count;
#else
  return true;
#endif
}
