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

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

#ifndef _PROXY_H_
#define _PROXY_H_

#include "simtypes.h"

#define PROXY_FLAG_SPEED_MORE 1  /* tried to use more traffic than the current limit */
#define PROXY_FLAG_SPEED_LESS 2  /* no longer wants to use more traffic than the limit */
#define PROXY_FLAG_SPEED_STOP 4  /* sleeping because of speed limit */
#define PROXY_FLAG_SPEED_TALK 8  /* clients are sleeping because of their speed limit */
#define PROXY_FLAG_SPEED_SEND 16 /* automatically send speed limit reports to server */
#define PROXY_FLAG_CONNECTED 16  /* local socket's peer has completed the TLS handshake */
#define PROXY_FLAG_ACCEPTED 32   /* local socket has been locally accepted by server */
#define PROXY_FLAG_COMPLETED 64  /* local socket's server has completed the TLS handshake */
#define PROXY_FLAG_INCOMING 128  /* incoming connection to anyone other than me AND from anyone other than me */
#define PROXY_FLAG_LOCAL 256     /* client or server is on my intranet or has the same ip address as me */

struct _customer {
  simcustomer next;     /* next customer in linked list - must be first element of structure */
  simsocket sock;       /* network socket */
  simcontact contact;   /* contact if any or NULL if no contact or not known */
  int count;            /* number of references */
  int type;             /* PROXY_TYPE_xxx */
  int flags;            /* PROXY_FLAG_xxx */
  unsigned number;      /* connection (client) number (256..65535) or zero */
  simunsigned announce; /* DHT announce when current tick not less than this (zero to not announce) */
  simunsigned reversed; /* tick of last reversal to proxy customer (zero if reversal currently running) */
  simunsigned sndtick;  /* timestamp of last sent UDP packet */
  int mode;             /* alpha/beta value for next DHT announce (MAIN_MODE_ACTIVE or MAIN_MODE_PASSIVE) */
  int version;          /* peer's protocol version or zero if not known */
  unsigned ip;          /* socket peer ip address */
  int port;             /* socket peer port */
  union {
    int proxyport; /* real proxy port (in case port is SIM_PROTO_PORT) only for PROXY_TYPE_PROXY */
    int error;     /* error to send to customer or SIM_OK if no error (only for PROXY_TYPE_SERVER) */
  };
  unsigned realip;       /* real ip address (only for local customers) */
  int realport;          /* socket port number (positive only for reversing or local customers) */
  int speed;             /* currently allocated speed for PROXY_TYPE_SERVER (if number is non-zero) or zero */
  simnumber measured[4]; /* tick when speed was last measured for PROXY_TYPE_SERVER */
  simnumber bytes[3];    /* traffic when speed was last measured for PROXY_TYPE_SERVER or PROXY_TYPE_CLIENT */
  int waiters[2];        /* number of waiting threads while PROXY_FLAG_STOP / PROXY_FLAG_TALK is set */
  int limit;             /* current speed limit (bytes per second) */
  int burst;             /* maximal speed limit (bytes per second) */
};

#define PROXY_TYPE_NEW 0       /* not yet known (incoming) */
#define PROXY_TYPE_CLIENT 1    /* client connected to customer */
#define PROXY_TYPE_SERVER 2    /* customer control connection */
#define PROXY_TYPE_ANONYMOUS 3 /* incoming traverser connection */
#define PROXY_TYPE_TRAVERSER 4 /* outgoing traverser connection */
#define PROXY_TYPE_REVERSE 5   /* cancellable outgoing traverser */
#define PROXY_TYPE_LOCAL 6     /* local socket for forwarding from remote client */
#define PROXY_TYPE_PROXY 7     /* control connection to proxy */

/* allocate a new proxy customer and its socket */
simcustomer proxy_customer_new (int type);

/* reference a customer. return customer */
simcustomer proxy_customer_acquire (simcustomer customer);

/* release customer but keep its socket */
void proxy_customer_remove (simcustomer customer);

/* re-announce single customer to the DHT */
void proxy_customer_announce (simcustomer server);

/* dereference a customer and synchronously disconnect it if no longer used; customer can be NULL */
void proxy_customer_release (simcustomer customer, int error); /* error is SIM_OK to release or not SIM_OK to cancel */

/* asynchronously cancel all customers and return their number. server must be NULL and error not SIM_OK */
int proxy_customer_cancel (simcustomer server, int error);

/* set contact of local customer */
void proxy_customer_set_contact (simcustomer customer, simcontact contact);

#define PROXY_STATS_SOCKETS 0          /* non-contact non-proxy socket statistics (previous runs) */
#define PROXY_STATS_SOCKET 1           /* non-contact non-proxy socket statistics (this run) */
#define PROXY_STATS_OUTPUTS 2          /* non-contact incoming socket statistics (previous runs) */
#define PROXY_STATS_OUTPUT 3           /* non-contact incoming socket statistics (this run) */
#define PROXY_STATS_INPUTS 4           /* non-contact incoming socket statistics (previous runs) */
#define PROXY_STATS_INPUT 5            /* non-contact incoming socket statistics (this run) */
#define PROXY_STATS_ALL 6              /* all-time statistics (incoming sockets) */
#define PROXY_STATS_THIS 7             /* data since start up (incoming sockets) */
#define PROXY_STATS_NOW 8              /* current statistics (incoming sockets) */
#define PROXY_STATS_DELETED_CLIENTS 9  /* CONTACT_STAT_CLIENTS (deleted, revoked and forgotten contacts) */
#define PROXY_STATS_DELETED_SOCKETS 10 /* CONTACT_STAT_SOCKETS (deleted, revoked and forgotten contacts) */
#define PROXY_STATS_DELETED_OUTPUTS 11 /* CONTACT_STAT_OUTPUTS (deleted, revoked and forgotten contacts) */
#define PROXY_STATS_DELETED_INPUTS 12  /* CONTACT_STAT_INPUTS (deleted, revoked and forgotten contacts) */

/* set statistic to value. i is PROXY_STATS_xxx, j is CONTACT_STAT_xxx */
void proxy_set_stat (simnumber value, unsigned i, unsigned j);

/* return socket statistic (for all non-contacts if contact is NULL). j is CONTACT_STAT_xxx */
simnumber proxy_get_stat_contact (simcontact contact, unsigned j);

/* return current data for a proxy customer including all its connected clients */
simnumber proxy_get_stat_server (simcustomer server, simnumber *received, simnumber *sent);

/* return proxy statistic. i is PROXY_STATS_xxx, j is CONTACT_STAT_xxx */
simnumber proxy_get_stat (unsigned i, unsigned j);
#define PROXY_GET_STAT(j) (proxy_get_stat (PROXY_STATS_NOW, j) + proxy_get_stat (PROXY_STATS_THIS, j))

/* check whether currently connected to proxy and return current connection to proxy or NULL if none.
   optionally return *ip and *port connected to, and proxy pong time in milliseconds as *latency */
simcustomer proxy_get_proxy (unsigned *ip, int *port, simnumber *latency);

/* return own ip address as *ip and local ip address as *localip (either can be zero if unknown) */
int proxy_get_ip (simsocket sock, unsigned *ip, unsigned *localip);

/* get real *ip and *port of currently used proxy and return false if no current proxy.
   can also optionally return proxy contact address */
simbool proxy_get_ip_proxy (const char **address, unsigned *ip, int *port);

/* return proxy latency or zero if not known. pingpongidx is CLIENT_PING_xxx / CLIENT_PONG_xxx */
simnumber proxy_get_latency (simclient client, unsigned pingpongidx);

/* find proxy customer by address */
simcustomer proxy_find_server (const char *address);

/* find PROXY_TYPE_CLIENT or PROXY_TYPE_LOCAL by optional address (if not NULL) and connection (client) number */
simcustomer proxy_find_client (const char *address, simnumber number, int type);

/* asynchronously cancel a customer. error must not be SIM_OK */
void proxy_cancel_server (simcustomer server, int error);

/* close connection to proxy if error not SIM_OK (SIM_PROXY_NOT_PREFERRED disconnects only if current proxy not preferred).
   if address is not NULL, close connection only if connected to that contact as a proxy (otherwise, always do) */
int proxy_cancel_proxy (const char *address, int error);

/* check if use of proxy is required. check only incoming connectivity if dht = false */
simbool proxy_check_required (simbool dht);

/* check if currently providing a proxy service */
simbool proxy_check_provided (void);

/* announce customers to the DHT (only scheduled ones if now = false, else announce all customers now) */
void proxy_announce (simunsigned tick, simbool now);

/* return a proxy server (if server = true) or client (if server = false) command packet to send.
   if key is NULL, value is ignored; else consumes value. caller must call table_free */
simtype proxy_new_cmd (const char *cmd, const char *key, simtype value, simbool server);

/* notify all customers (servers) about going offline */
void proxy_send_error (int error);

/* process a received proxy client command */
int proxy_recv_cmd_ (simsocket sock, const simtype table, simbool proxy);

/* process a received UDP packet */
int proxy_recv_udp (simclient client, const simtype input, unsigned ip, int port);

/* send ping packet to proxy (NULL client for current proxy) */
int proxy_ping (simclient client);

/* send client version and receive server version. proxyip:proxyport (ip and port of my proxy) are sent to peer */
int proxy_handshake_client_ (simsocket sock, const char *address, unsigned proxyip, int proxyport,
                             unsigned *ip, int *port, int *protover);

/* connect to proxy */
int proxy_connect_ (simcustomer proxy, simsocket lock, unsigned ip, int port, unsigned *ownip, int *ownport);

/* main proxy loop (customer side). return SIM_NO_ERROR if no connection could be established for long */
int proxy_loop_proxy_ (const char *address, unsigned ip, int port, unsigned senderip);

/* send server version and receive client version. peer's ip is returned as *ip  */
int proxy_handshake_server_ (simcustomer customer, unsigned *ip);

/* internal use only */

/* debug logging */
void proxy_log_customers (const char *module, int level);

/* called when server port is opened or closed. also called on ip change (init < 0) */
void proxy_init_stat (int init);

/* initialize and deinitialize proxy module */
int proxy_init (simbool init);
void proxy_uninit (void);

#define PROXY_NUMBER_MIN 256   /* minimal proxy connection (client) number */
#define PROXY_NUMBER_MAX 65535 /* maximal proxy connection (client) number */

#define proxy_get_random() (random_get_number (random_public, PROXY_NUMBER_MAX - PROXY_NUMBER_MIN) + PROXY_NUMBER_MIN)

#define PROXY_STAT_TOTAL 0      /* all proxy traffic */
#define PROXY_STAT_HANDSHAKES 1 /* unknown traffic (failed handshakes) */

#define PROXY_ADDRESS_CONTROL "PROXY"       /* fake address of proxy control connection */
#define PROXY_ADDRESS_TRAVERSER "TRAVERSER" /* fake address of proxy traverser connection */
#define PROXY_ADDRESS_PROXY "DHT"           /* fake address of connection to proxy (for main_search_start) */

#define PROXY_SPEED_AUDIO 8192 /* minimal speed to request from proxy */

extern simcustomer proxy_customer_list;

extern unsigned proxy_localip[3];
#define PROXY_CHECK_LOCAL(ip) (proxy_localip[0] == (ip) || proxy_localip[1] == (ip) || proxy_localip[2] == (ip))

#endif
