/* Copyright (c) 2020-2022 The Creators of Simphone

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

#ifndef _SOCKET_H_
#define _SOCKET_H_

#include "simtypes.h"

struct _system_qos {
  int tos;
#ifdef _WIN32
  unsigned flowid;
  void *handle;
#endif
};

struct _socket_crypt {
  void *decrypt;         /* decrypt context (see crypto.h) */
  void *encrypt;         /* encrypt context (see crypto.h) */
  int padding;           /* packet padding boundary */
  int maxsize;           /* maximal packet size to send */
  unsigned decryptsize;  /* minimal tag/IV size for decrypt */
  unsigned encryptsize;  /* minimal tag/IV size for encrypt */
  simnumber decryptseq;  /* SSL sequence number for receive */
  simnumber encryptseq;  /* SSL sequence number for send */
  const char *rcvcipher; /* decrypt cipher name */
  const char *sndcipher; /* encrypt cipher name */
  simtype ivs;           /* unique initialization vectors or nil if random */
};

#define SOCKET_FLAG_NO_BLOCK 1   /* do not send data if socket would block */
#define SOCKET_FLAG_NO_SESSION 2 /* do not add session to statistics on connect */
#define SOCKET_FLAG_CLIENT 4     /* socket has been assigned to a client */
#define SOCKET_FLAG_HANDSHAKE 8  /* socket has been authenticated successfully */
#define SOCKET_FLAG_LOCAL 16     /* socket is a proxy connection with self */
#define SOCKET_FLAG_PONG 32      /* SIM_CLIENT_CMD_PONG has been received */
#define SOCKET_FLAG_PING_PONG 64 /* SIM_CLIENT_CMD_PING will be replied to */
#define SOCKET_FLAG_PING 128     /* SIM_CLIENT_CMD_PING can be received */
#define SOCKET_FLAG_CONNECT 256  /* socket has been connected successfully */
#define SOCKET_FLAG_CPU 512      /* measure and ignore cpu load on socket */

struct _socket {
  int fd;                 /* socket descriptor or INVALID_SOCKET if not open */
  int err;                /* not SIM_OK if socket was cancelled (scheduled for immediate closing) */
  int udport;             /* destination port for UDP communication or zero if TCP only */
  unsigned ip;            /* destination ip address for UDP communication (if udport positive) or zero */
  struct _system_qos qos; /* type of service currently set to the socket */
  simcustomer local;      /* localhost proxy customer or NULL if not connected to one */
  int flags;              /* SOCKET_FLAG_xxx */
  int rcvtimeout;         /* receive timeout value in seconds */
  int sndtimeout;         /* send timeout value in seconds */
  int maxmb;              /* maximal number of megabytes to receive and send before disconnect */
  simnumber rcvbytes;     /* number of bytes received from socket */
  simnumber sndbytes;     /* number of bytes sent to socket */
  simnumber rcvheaders;   /* number of packet header bytes received from socket */
  simnumber sndheaders;   /* number of packet header bytes sent to socket */
  struct _socket_stat {
    simnumber rcvbytes;   /* number of bytes previously received from socket */
    simnumber sndbytes;   /* number of bytes previously sent to socket */
    simnumber rcvheaders; /* number of packet header bytes previously received from socket */
    simnumber sndheaders; /* number of packet header bytes previously sent to socket */
  } old;
  simnumber created;          /* tick when socket was opened or accepted or zero if not or if local */
  simnumber pinged;           /* tick when internet speed was measured with socket (zero if never measured) */
  void *lock;                 /* pth_mutex_t for protecting socket_send_table_, so it always arrives in one piece */
  struct _ssl_master *master; /* TLS handshake context */
  struct _socket_crypt crypt; /* crypto context */
  struct _socket_crypt proxy; /* proxy crypto context (client-side only) */
  simclient client;           /* client this socket belongs to or NULL or SOCKET_NO_SESSION if proxy */
  simtype buffer;             /* buffer for proxy SSL (client-side only) */
  unsigned bufferlen;         /* number of bytes currently left in buffer */
};

#define SOCKET_MAX_NEW 0     /* maximal number of new sockets */
#define SOCKET_MAX_FDS 1     /* maximal number of reserved sockets */
#define SOCKET_MAX_CLIENTS 2 /* maximal number of client/msg sockets */
#define SOCKET_MAX_PROBES 3  /* maximal number of probe sockets */
#define SOCKET_MAX_PROXY 4   /* maximal number of proxy clients+servers */
#define SOCKET_MAX_MTU 5     /* value of socket.mtu parameter */
#define SOCKET_MAX_WAIT 6    /* value of socket.wait parameter */
#define SOCKET_MAX_SOCKETS 7 /* maximal number of sockets */

extern int socket_max_limit[]; /* indexed by SOCKET_MAX_xxx */

#ifdef _WIN32
#define SOCKET_MAX_FACTOR 1 /* one file (socket) descriptor per client */
#elif ! defined(__APPLE__)
#define SOCKET_MAX_FACTOR 2 /* a second descriptor to open file while transferring */
#else
#define SOCKET_MAX_FACTOR 5 /* three additional descriptors for nat, msg and xfer queues */
#endif

/* initialize and deinitialize sockets */
int socket_init (void);
void socket_uninit (void);

/* open socket. client can be SOCKET_NO_SESSION to not add connection count and duration to statistics */
simsocket socket_new (simsocket sock);      /* create a new unopened (invalid) socket */
int socket_create (simsocket sock, int fd); /* create socket from file descriptor */
int socket_open (simsocket sock, simclient client);
int socket_reopen (simsocket sock, simclient client, simcontact contact, int error);

/* return socket's peer address. output ip and port in host byte-order */
#define socket_get_peer(sock, ip, port) socket_get_name (sock, ip, port, true)
#define socket_get_addr(sock, ip, port) socket_get_name (sock, ip, port, false)
int socket_get_name (simsocket sock, unsigned *ip, int *port, simbool peer);

/* return SOCKET_NOT_LOCKED if socket_connect_ will not wait on the global connect lock (sock is NULL) */
int socket_get_lock (simsocket sock);
#define SOCKET_NOT_LOCKED 0x7FFFFFFF

/* return socket statistic. j is CONTACT_STAT_xxx */
simnumber socket_get_stat (simsocket sock, unsigned j, simbool oldstat);

/* return real socket statistic. j is CONTACT_STAT_xxx */
#define SOCKET_GET_STAT_SOCKET(oldsock) \
  (socket_check_server (oldsock) ? socket_check_server (oldsock)->sock : (oldsock))
#define SOCKET_GET_STAT(sock, j) \
  (socket_get_stat ((j) > CONTACT_STAT_SENT ? (sock) : SOCKET_GET_STAT_SOCKET (sock), j, false))

#define SOCKET_GET_STAT_RECEIVED(sock) ((sock)->rcvbytes + (sock)->rcvheaders)
#define SOCKET_GET_STAT_SENT(sock) ((sock)->sndbytes + (sock)->sndheaders)

#define SOCKET_HEADER_UDP 28 /* assumed UDP header size */
#define SOCKET_HEADER_TCP 52 /* assumed TCP header size */
#define SOCKET_HEADER_FIN 52 /* assumed TCP header size for FIN packets */
#define SOCKET_HEADER_SYN 60 /* assumed TCP header size for SYN packets */

#define SOCKET_ADD_STAT(sock, oldsock)                                              \
  (sock)->rcvbytes += (oldsock)->rcvbytes, (sock)->sndbytes += (oldsock)->sndbytes, \
    (sock)->rcvheaders += (oldsock)->rcvheaders, (sock)->sndheaders += (oldsock)->sndheaders
#define SOCKET_INIT_STAT(sock) (sock)->sndheaders = (sock)->rcvheaders = (sock)->sndbytes = (sock)->rcvbytes = 0

extern struct _socket socket_udp_sock; /* the one and only socket descriptor for UDP communication */

/* set the one and only socket descriptor for UDP communication */
void socket_set_udp (int fd);

/* set socket's send or recv (if send = false) buffer size or return the current size if size = 0 */
int socket_set_buffer (simsocket sock, int size, simbool receive);
/* set TOS (type of service) to socket */
void socket_set_qos (simsocket sock, const void *sin, int tos);

/* allow socket to reuse existing port number */
int sim_socket_set_reuse (int fd);
/* set socket to blocking or non-blocking mode */
int sim_socket_set_nonblock (int fd, unsigned long nonblock);

#define SOCKET_NO_SESSION (void *) 1    /* contact of socket_destroy that does not add a session to statistics */
#define SOCKET_PROXY_SESSION (void *) 2 /* contact of socket_destroy that adds a proxy session to statistics */

/* close socket and unless contact is SOCKET_NO_SESSION, add statistics to contact */
void socket_destroy (simsocket sock, simcontact contact, int linger);
#define socket_close(sock, contact) socket_destroy (sock, contact, SOCKET_LINGER)
int socket_close_lock_ (simsocket sock, simcontact contact); /* close after any operation in progress has finished */

#define SOCKET_NO_LINGER 0    /* do not enter TIME_WAIT state */
#define SOCKET_LINGER (-1)    /* normal close */
#define SOCKET_NO_UNLOCK (-2) /* do not unlock if holding the lock - a second close without this value will unlock */

/* acquire and release the socket mutex (internal use only) */
int _socket_lock_ (simsocket sock, int useconds, const char *function);
#define socket_lock_(sock, useconds) _socket_lock_ (sock, useconds, __FUNCTION__)
int _socket_unlock (simsocket sock, const char *function, int fd);
#define socket_unlock(sock, fd) _socket_unlock (sock, __FUNCTION__, fd)

/* change TCP socket descriptor to listening mode. last three arguments like socket_listen_tcp */
int socket_listen_port (int fd, int backlog, unsigned ip, int port, int *newport);
/* create listening socket. if newport is non-NULL, may change port and return the new one. ip and port in host byte-order */
int socket_listen_tcp (simsocket sock, unsigned ip, int port, int *newport);
/* also return *fd */
int socket_listen_udp (unsigned ip, int port, int *fd, int *newport);
/* create a new socket for accepted incoming TCP connection. return SIM_SOCKET_ACCEPT to retry. caller needs to close newsock */
int socket_accept (simsocket sock, simsocket newsock, unsigned *ip, int *port);

#ifdef _WIN32
#define socket_get_errno() WSAGetLastError ()
#define socket_set_errno(error) WSASetLastError (error)
#define close_socket(fd) closesocket (fd)
#else
#define socket_get_errno() errno
#define socket_set_errno(error) errno = (error)
#define close_socket(fd) close (fd)
#define INVALID_SOCKET (-1)
#endif

#define socket_cancel(sock, error) ((sock)->err = (error)) /* close socket asynchronously */

/* check if a UDP packet is a valid DHT packet */
#define SOCKET_CHECK_MAINLINE(input) ((input)[0] == 'd' && (input)[2] == ':' && (input)[1] >= '1' && (input)[1] <= '9')

#define socket_check_client(sock) (sock)->proxy.decrypt /* check if socket does double encryption */
#define socket_check_server(sock) (sock)->local         /* check if socket connected to local proxy customer */
#define SOCKET_CHECK_PROXY(sock) (socket_check_client (sock) || socket_check_server (sock))

/* overhead of SIM_SERVER_DATA packet with Twofish IV and tag (RANDOM_SIZE_BLOCK * 2) and connection number (two bytes) */
#define SOCKET_OVERHEAD_SSL 5 /* number of bytes in TLS header */
#define SOCKET_OVERHEAD_PROXY (2 + RANDOM_SIZE_BLOCK * 2 + SOCKET_OVERHEAD_SSL)

/* estimate number of packet headers */
#define SOCKET_CALC_OVERHEAD(length, hdr) \
  ((((length) + socket_max_limit[SOCKET_MAX_MTU] - (hdr) - (1)) / (socket_max_limit[SOCKET_MAX_MTU] - (hdr))) * (hdr))
#define SOCKET_CALC_OVERHEAD_TCP(length) SOCKET_CALC_OVERHEAD (length, SOCKET_HEADER_TCP)
#define SOCKET_CALC_OVERHEAD_UDP(length) SOCKET_CALC_OVERHEAD (length, SOCKET_HEADER_UDP)

/* check if sockets are or become readable within the specified time (mseconds < 0 for infinite wait)
   return -1 if errno is set, 0 = neither readable, 1 = fd readable, 2 = pipefd readable, 3 = both readable */
int socket_select_pipe_ (int fd, int pipefd, int mseconds);
/* check if socket is or becomes readable within the specified time. return -1 if errno is set, or else simbool */
int socket_select_readable_ (int fd, int seconds, int mseconds);
/* check if socket is or becomes writable within the specified time. return -1 if errno is set, or else simbool */
#define socket_select_writable(fd) socket_select_writable_ (fd, 0, 0)
int socket_select_writable_ (int fd, int seconds, int mseconds);

/* bits for socket_recv_xxx */
#define SOCKET_RECV_TCP 0   /* default */
#define SOCKET_RECV_PROXY 1 /* force single decryption */
#define SOCKET_RECV_UDP 4   /* add statistics to local customer */
#define SOCKET_RECV_SSL 8   /* force double recv timeout */

/* receive bytes from socket and return actual *length */
int socket_recv_some_ (simsocket sock, int bits, simtype output, unsigned *length);
/* receive all bytes from socket; add number of received header bytes to *headers */
int _socket_recv_all_ (simsocket sock, unsigned *headers, int bits, simtype output, const char *file, unsigned line);
#define socket_recv_all_(sock, bits, output) _socket_recv_all_ (sock, NULL, bits, output, __FUNCTION__, __LINE__)

/* receive SSL packet from TCP socket. returned *output is SIMSTRING; return packet header bytes as *headers */
int _socket_recv_ssl_ (simsocket sock, int bits, simtype *output, unsigned *headers, const char *file, unsigned line);
#define socket_recv_ssl_(sock, bits, output) _socket_recv_ssl_ (sock, bits, output, NULL, __FUNCTION__, __LINE__)
/* receive packet from proxy (client). returned *output is SIMSTRING */
int _socket_recv_proxy_ (simsocket sock, int bits, simtype *output, const char *file, unsigned line);
#define socket_recv_proxy_(sock, bits, output) _socket_recv_proxy_ (sock, bits, output, __FUNCTION__, __LINE__)

/* create table from data received from (UDP) socket */
int _socket_recv_table (simsocket sock, const simtype proto, const simtype proto2, simnumber sequence,
                        const simtype input, int bits, simtype *table, const char *file, unsigned line);
#define socket_recv_table(sock, proto, proto2, sequence, input, bits, table) \
  _socket_recv_table (sock, proto, proto2, sequence, input, bits, table, __FUNCTION__, __LINE__)

/* receive table from a TCP socket using the given protocol template */
int _socket_recv_table_ (simsocket sock, const simtype proto, const simtype proto2,
                         simtype *table, unsigned *bytes, unsigned *headers, const char *file, unsigned line);
#define socket_recv_table_(sock, proto, proto2, table, bytes) \
  _socket_recv_table_ (sock, proto, proto2, table, bytes, NULL, __FUNCTION__, __LINE__)

/* bits for socket_send_xxx */
#define SOCKET_SEND_TCP 0   /* default */
#define SOCKET_SEND_PROXY 1 /* from client to proxy */
#define SOCKET_SEND_AUDIO 2 /* no packet padding */
#define SOCKET_SEND_UDP 4   /* fixed sequence number */
#define SOCKET_SEND_SSL 8   /* allow sending large packets */
#define SOCKET_SEND_PAD 16  /* pad proxy packets */

/* send all bytes to socket */
int socket_send_all_ (simsocket sock, const simbyte *input, unsigned length);

/* send packet to proxy (client) */
int _socket_send_proxy_ (simsocket sock, const simtype input, int bits, const char *file, unsigned line);
#define socket_send_proxy_(sock, input, bits) _socket_send_proxy_ (sock, input, bits, __FUNCTION__, __LINE__)

/* send table to TCP socket */
int socket_send_table_ (simsocket sock, const simtype table, int bits, unsigned *bytes);
/* send table if socket is not busy; return SIM_SOCKET_BLOCKED otherwise */
int socket_send (simsocket sock, const simtype table, int bits, unsigned *bytes);

/* send table to UDP socket */
int socket_send_udp (simsocket sock, const simtype table, simnumber sequence, unsigned ip, int port, int bits,
                     unsigned *bytes);

/* return maximum allowed data size (in bytes) that can be sent over this socket. bits is SOCKET_SEND_xxx */
unsigned socket_size_max (simsocket sock, int bits);
/* return maximum table size that should be sent over this socket */
unsigned socket_size_client (simsocket sock, int padding, int *size);
/* return table size if it would be sent to TCP socket without actually sending it. bits is SOCKET_SEND_xxx */
unsigned socket_size_table (simsocket sock, const simtype table, int bits);

/* connect to host if not NULL and return ip. if host is NULL, connect to ip. */
int socket_connect_socks_ (simsocket sock, simsocket lock, unsigned *ip, int port,
                           const char *host, int command, simbool local);
#define socket_connect_lock_(sock, lock, ip, port, host) socket_connect_socks_ (sock, lock, ip, port, host, 1, false)
#define socket_connect_(sock, ip, port, host, local) socket_connect_socks_ (sock, NULL, ip, port, host, 1, local)

/* debug logging */
extern simtype socket_table; /* keyed by fd value is SIMPOINTER struct thread_info */
#define SOCKET_ADD(fd) table_set_key (socket_table, string_copy_len (&(fd), sizeof (fd)), pth_thread_acquire ())
#define SOCKET_DELETE(fd) pth_thread_release (table_detach_key (socket_table, pointer_new_len (&(fd), sizeof (fd))))

void socket_log_thread (const char *module, int level, const void *thread);

#endif
