/*
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * https://opensource.org/license/bsd-3-clause
 *
 * Copyright (C) 2011, 2012, 2013 Citrix Systems
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * IO Abstraction library
 */

#ifndef __IOA_LIBIMPL__
#define __IOA_LIBIMPL__

#include "ns_turn_ioalib.h" // IWYU pragma: export

#include "ns_turn_openssl.h"

#include "ns_turn_maps.h"
#include "ns_turn_maps_rtcp.h"
#include "ns_turn_server.h"
#include "turn_ports.h"

#include "apputils.h"
#include "stun_buffer.h"
#include "userdb.h"

#include "ns_sm.h"

#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>

#include <event2/thread.h>

#include <pthread.h>

#ifdef __cplusplus
extern "C" {
#endif

//////////////////////////////////////////////////////

#define MAX_BUFFER_QUEUE_SIZE_PER_ENGINE (64)
#define MAX_SOCKET_BUFFER_BACKLOG (16)
#define IOA_UDP_RECVMMSG_MAX_BATCH (16)
#define IOA_UDP_SENDMMSG_MAX_BATCH (32)

#define BUFFEREVENT_HIGH_WATERMARK (128 << 10)
#define BUFFEREVENT_MAX_UDP_TO_TCP_WRITE (64 << 9)
#define BUFFEREVENT_MAX_TCP_TO_TCP_WRITE (192 << 10)

typedef unsigned char recv_ttl_t;
typedef unsigned char recv_tos_t;

typedef struct _stun_buffer_list_elem {
  struct _stun_buffer_list_elem *next;
  stun_buffer buf;
} stun_buffer_list_elem;

typedef struct _stun_buffer_list {
  stun_buffer_list_elem *head;
  stun_buffer_list_elem *tail;
  size_t tsz;
} stun_buffer_list;

/*
 * New connection callback
 */

struct cb_socket_message {
  turnserver_id id;
  tcp_connection_id connection_id;
  stun_tid tid;
  ioa_socket_handle s;
  int message_integrity;
  ioa_net_data nd;
  int can_resume;
};

struct cancelled_session_message {
  turnsession_id id;
};

struct relay_server {
  turnserver_id id;
  super_memory_t *sm;
  struct event_base *event_base;
  struct bufferevent *in_buf;
  struct bufferevent *out_buf;
  struct bufferevent *auth_in_buf;
  struct bufferevent *auth_out_buf;
  ioa_engine_handle ioa_eng;
  turn_turnserver server;
  pthread_t thr;
};

struct message_to_relay {
  MESSAGE_TO_RELAY_TYPE t;
  struct relay_server *relay_server;
  union {
    struct socket_message sm;
    struct cb_socket_message cb_sm;
    struct cancelled_session_message csm;
  } m;
};

struct relay_server;
typedef struct relay_server *relay_server_handle;

typedef int (*ioa_engine_new_connection_event_handler)(ioa_engine_handle e, struct message_to_relay *sm);
typedef int (*ioa_engine_udp_event_handler)(relay_server_handle rs, struct message_to_relay *sm);

#define TURN_CMSG_SZ (65536)

#define PREDEF_TIMERS_NUM (14)
extern const int predef_timer_intervals[PREDEF_TIMERS_NUM];

struct _ioa_engine {
  super_memory_t *sm;
  struct event_base *event_base;
  int deallocate_eb;
  int verbose;
  turnipports *tp;
  rtcp_map *map_rtcp;
  stun_buffer_list bufs;
  SSL_CTX *tls_ctx;
  SSL_CTX *dtls_ctx;
  turn_time_t jiffie; /* bandwidth check interval */
  ioa_timer_handle timer_ev;
  char cmsg[TURN_CMSG_SZ + 1];
  int predef_timer_intervals[PREDEF_TIMERS_NUM];
  struct timeval predef_timers[PREDEF_TIMERS_NUM];
  /* Relays */
  char relay_ifname[1025];
  int default_relays;
  size_t relays_number;
  size_t relay_addr_counter;
  ioa_addr *relay_addrs;
#if defined(__linux__)
  struct ioa_socket_recvmmsg_state *udp_recvmmsg_state;
  uint64_t udp_recvmmsg_calls;
  uint64_t udp_recvmmsg_packets;
  uint64_t udp_recvmmsg_wouldblock;
  uint64_t udp_recvmmsg_unavailable;
  uint64_t udp_recvmmsg_no_buffer;
  uint64_t udp_recvmmsg_hist[IOA_UDP_RECVMMSG_MAX_BATCH + 1];
  uint64_t udp_recvmmsg_last_report_calls;
  turn_time_t udp_recvmmsg_last_report_time;
  /* sendmmsg / UDP-GSO egress batching stats (see --udp-sendmmsg-log) */
  uint64_t udp_sendmmsg_flushes;                              /* batch flushes carrying >=1 datagram */
  uint64_t udp_sendmmsg_datagrams;                            /* total datagrams handed to the kernel via batches */
  uint64_t udp_sendmmsg_gso_flushes;                          /* flushes that coalesced via a single UDP-GSO sendmsg */
  uint64_t udp_sendmmsg_gso_datagrams;                        /* datagrams coalesced via UDP-GSO */
  uint64_t udp_sendmmsg_hist[IOA_UDP_SENDMMSG_MAX_BATCH + 1]; /* per-flush occupancy histogram */
  uint64_t udp_sendmmsg_last_report_flushes;
  turn_time_t udp_sendmmsg_last_report_time;
#endif
  redis_context_handle rch;
  /* multiplex-peer (zero-initialised = disabled) */
  int mp_enabled;
  int relay_thread_id;
  ioa_socket_handle mp_sock_v4;
  ioa_socket_handle mp_sock_v6;
  uint16_t mp_port_v4;
  uint16_t mp_port_v6;
  ur_addr_map mp_table; /* peer_addr:port -> ts_ur_super_session*; O(1) get/put/del */
};

#define SOCKET_MAGIC (0xABACADEF)

struct traffic_bytes {
  band_limit_t jiffie_bytes_read;
  band_limit_t jiffie_bytes_write;
};

struct _ioa_socket {
  evutil_socket_t fd;
  struct _ioa_socket *parent_s;
  uint32_t magic;
  ur_addr_map *sockets_container; /* relay container for UDP sockets */
  struct bufferevent *bev;
  ioa_network_buffer_handle defer_nbh;
  int family;
  SOCKET_TYPE st;
  SOCKET_APP_TYPE sat;
  SSL *ssl;
  uint32_t ssl_renegs;
  int in_write;
  int bound;
  int local_addr_known;
  ioa_addr local_addr;
  int connected;
  ioa_addr remote_addr;
  ioa_engine_handle e;
  struct event *read_event;
  ioa_net_event_handler read_cb;
  void *read_ctx;
  /* recvmmsg batch-receive eligibility: set once at creation for the shared
   * fan-in sockets (the client listener and the multiplex-peer relay socket).
   * Per-session relay sockets stay false, so socket_input_worker() keeps them on
   * the single-recv path where batching would only add buffer churn. Checked on
   * the read hot path (Linux only) but kept unconditional to avoid guarding the
   * set-sites. Defaults to false via the calloc() zero-init every ioa_socket
   * gets; only the shared sockets flip it true. */
  bool udp_recvmmsg_eligible;
  int done;
  ts_ur_super_session *session;
  int current_df_relay_flag;
  /* RFC6156: if IPv6 is involved, do not use DF: */
  int do_not_use_df;
  int tobeclosed;
  int broken;
  int default_ttl;
  int current_ttl;
  int default_tos;
  int current_tos;
  stun_buffer_list bufs;
  turn_time_t jiffie; /* bandwidth check interval */
  struct traffic_bytes data_traffic;
  struct traffic_bytes control_traffic;
  /* RFC 6062 ==>> */
  // Connection session:
  tcp_connection *sub_session;
  // Connect:
  struct bufferevent *conn_bev;
  connect_cb conn_cb;
  void *conn_arg;
  // Accept:
  struct evconnlistener *list_ev;
  accept_cb acb;
  void *acbarg;
  /* <<== RFC 6062 */
  void *special_session;
  size_t special_session_size;
};

typedef struct _timer_event {
  struct event *ev;
  ioa_engine_handle e;
  ioa_timer_event_handler cb;
  void *ctx;
  char *txt;
} timer_event;

///////////////////////////////////

/* realm */

void create_default_realm(void);
int get_realm_data(char *name, realm_params_t *rp);

/* multiplex-peer */

int init_multiplex_peer(ioa_engine_handle e, int thread_id, uint16_t base_port);
int mp_register_peer(ioa_engine_handle e, const ioa_addr *peer_addr, void *turn_session);
void mp_deregister_peer(ioa_engine_handle e, const ioa_addr *peer_addr, void *turn_session);
void mp_deregister_permission_peers(ioa_engine_handle e, const ioa_addr *peer_addr, void *turn_session);
void mp_deregister_session_peers(ioa_engine_handle e, void *turn_session, int address_family);
ioa_socket_handle mp_get_socket(ioa_engine_handle e, int af);
uint16_t mp_get_port(ioa_engine_handle e, int af);

/* engine handling */

ioa_engine_handle create_ioa_engine(super_memory_t *sm, struct event_base *eb, turnipports *tp, const char *relay_if,
                                    size_t relays_number, char **relay_addrs, int default_relays, int verbose
#if !defined(TURN_NO_HIREDIS)
                                    ,
                                    redis_stats_db_t *redis_stats_db
#endif
);

void ioa_engine_set_rtcp_map(ioa_engine_handle e, rtcp_map *rtcpmap);

ioa_socket_handle create_ioa_socket_from_fd(ioa_engine_handle e, ioa_socket_raw fd, ioa_socket_handle parent_s,
                                            SOCKET_TYPE st, SOCKET_APP_TYPE sat, const ioa_addr *remote_addr,
                                            const ioa_addr *local_addr);
ioa_socket_handle create_ioa_socket_from_ssl(ioa_engine_handle e, ioa_socket_handle parent_s, SSL *ssl, SOCKET_TYPE st,
                                             SOCKET_APP_TYPE sat, const ioa_addr *remote_addr,
                                             const ioa_addr *local_addr);

int get_a_local_relay(int family, ioa_addr *relay_addr);

void add_socket_to_parent(ioa_socket_handle parent_s, ioa_socket_handle s);
void delete_socket_from_parent(ioa_socket_handle s);

void add_socket_to_map(ioa_socket_handle s, ur_addr_map *amap);
void delete_socket_from_map(ioa_socket_handle s);

int is_connreset(void);
int would_block(void);
void udp_sendmmsg_batch_begin(void);
void udp_sendmmsg_batch_end(void);
int udp_send(ioa_socket_handle s, const ioa_addr *dest_addr, const char *buffer, int len);
int udp_recvfrom(evutil_socket_t fd, ioa_addr *orig_addr, const ioa_addr *like_addr, char *buffer, int buf_size,
                 int *ttl, int *tos, char *ecmsg, int flags, uint32_t *errcode);
int ssl_read(evutil_socket_t fd, SSL *ssl, ioa_network_buffer_handle nbh, int verbose);

#if defined(__linux__)
void ioa_init_recvmmsg_hdr(struct mmsghdr *msg, struct iovec *iov, ioa_addr *src_addr, char *cmsg, size_t cmsg_len,
                           socklen_t slen, void *buf, size_t len);
#endif

#if !defined(_MSC_VER) && defined(CMSG_SPACE)
void ioa_parse_udp_recvmsg_cmsg(struct msghdr *msg, int *ttl, int *tos, uint32_t *errcode);
#endif

#if defined(__linux__)
void ioa_engine_record_udp_recvmmsg_batch(ioa_engine_handle e, int rc);
void ioa_engine_record_udp_recvmmsg_wouldblock(ioa_engine_handle e);
void ioa_engine_record_udp_recvmmsg_unavailable(ioa_engine_handle e);
void ioa_engine_record_udp_recvmmsg_no_buffer(ioa_engine_handle e);
void ioa_engine_record_udp_sendmmsg_flush(ioa_engine_handle e, unsigned int count, unsigned int gso_count);
#endif

int set_raw_socket_ttl_options(evutil_socket_t fd, int family);
int set_raw_socket_tos_options(evutil_socket_t fd, int family);

int set_socket_options_fd(evutil_socket_t fd, SOCKET_TYPE st, int family);
int set_socket_options(ioa_socket_handle s);

int send_session_cancellation_to_relay(turnsession_id sid);

int send_data_from_ioa_socket_tcp(ioa_socket_handle s, const void *data, size_t sz);
int send_str_from_ioa_socket_tcp(ioa_socket_handle s, const void *data);
int send_ulong_from_ioa_socket_tcp(ioa_socket_handle s, size_t data);

int ioa_socket_check_bandwidth(ioa_socket_handle s, ioa_network_buffer_handle nbh, int read);

///////////////////////// SUPER MEMORY ////////

#define allocate_super_memory_engine(e, size)                                                                          \
  allocate_super_memory_engine_func(e, size, __FILE__, __FUNCTION__, __LINE__)
void *allocate_super_memory_engine_func(ioa_engine_handle e, size_t size, const char *file, const char *func, int line);

/////////////////////////////////////////////////

#ifdef __cplusplus
}
#endif

#endif /* __IOA_LIBIMPL__ */
