/*
    Copyright (C) 2008-2024, Millistream Market Data <support@millistream.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#ifndef MDF_COMMON_H
# define MDF_COMMON_H 1
# define WINVER 0x0600 /* so we can use some functions on Windows... */
# define _WIN32_WINNT 0x0600 /* so we can use some functions on Windows... */

# ifdef HAVE_CONFIG_H
#  include "../config.h"
# endif
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
# ifdef HAVE_UNISTD_H
#  include <unistd.h>
# endif
# include <stdio.h>
# ifdef HAVE_STDINT_H
#  include <stdint.h>
# elif _MSC_VER >= 1600
#  include <stdint.h>
#  define _STDINT_H 1 /* so we ignore the check in mdf.h */
# elif _MSC_VER
#  define uint8_t unsigned __int8
#  define uint16_t unsigned __int16
#  define uint32_t unsigned __int32
#  define uint64_t unsigned __int64
#  define int64_t __int64
#  define _STDINT_H 1 /* so we ignore the check in mdf.h */
# else
#  define uint8_t unsigned char
#  define uint16_t unsigned short
#  define uint32_t unsigned int
#  define uint64_t unsigned long long
#  define int64_t long long
#  define _STDINT_H 1 /* so we ignore the check in mdf.h */
# endif

# ifdef HAVE_INTTYPES_H
#  include <inttypes.h>
# elif _MSC_VER
#  define PRIu64 "I64u"
# else
#  define PRIu64 "llu"
# endif

# ifndef __UINT64_C
#  ifdef UINT64_C
#   define __UINT64_C(c) UINT64_C(c)
#  elif _MSC_VER
#   define __UINT64_C(c) c##ui64
#  else
#   define __UINT64_C(c) c##ULL
#  endif
# endif

# ifndef __INT64_C
#  ifdef INT64_C
#   define __INT64_C(c) INT64_C(c)
#  elif _MSC_VER
#   define __INT64_C(c) c##i64
#  else
#   define __INT64_C(c) c##LL
#  endif
# endif

# ifndef HAVE_STRTOULL
#  if _MSC_VER >= 1300
#   define strtoull(a,b,c) _strtoui64(a,b,c)
#  elif HAVE_STRTOQ
#   define strtoull(a,b,c) strtoq(a,b,c)
#  else
#   define strtoull(a,b,c) strtoul(a,b,c)
#  endif
# endif

# ifdef HAVE_STRING_H
#  include <string.h>
# endif
# ifdef HAVE_SYS_IOCTL_H
#  include <sys/ioctl.h>
# endif
# ifdef HAVE_SYS_FILIO_H
#  include <sys/filio.h>
# endif
# ifdef HAVE_SYS_TYPES_H
#  include <sys/types.h>
# endif
# ifdef HAVE_SYS_SOCKET_H
#  include <sys/socket.h>
# endif
# ifdef HAVE_SYS_UN_H
#  include <sys/un.h>
# endif
# ifdef HAVE_NETINET_IN_H
#  include <netinet/in.h>
# endif
# ifdef HAVE_NETINET_TCP_H
#  include <netinet/tcp.h>
# endif
# ifdef HAVE_NETDB_H
#  include <netdb.h>
# endif
# ifdef HAVE_FCNTL_H
#  include <fcntl.h>
# endif
# ifdef HAVE_ERRNO_H
#  include <errno.h>
# endif
# ifdef HAVE_SIGNAL_H
#  include <signal.h>
# endif
# if defined _WIN32 || defined _WIN64
#  include <stddef.h>
#  include <limits.h>
#  include <winsock2.h>
#  include <ws2tcpip.h>
#  define MSG_NOSIGNAL 0
# else
#  define INVALID_SOCKET -1
# endif
# include <time.h>
# include <ctype.h>
# include <stdarg.h>
# include <zlib.h>
# include <zstd.h>
# include <stdatomic.h>

# include <openssl/opensslv.h>

# ifdef LIBRESSL_VERSION_NUMBER
#  undef OPENSSL_VERSION_NUMBER
#  undef OPENSSL_VERSION_PREREQ
#  define OPENSSL_VERSION_NUMBER 0x1000107fL /* 1.0.1 */
# endif

# ifndef OPENSSL_VERSION_PREREQ
#  if OPENSSL_VERSION_NUMBER >= 0x30000000L /* 3.0.0 */
#   define OPENSSL_VERSION_MAJOR 3
#   define OPENSSL_VERSION_MINOR 0
#  elif OPENSSL_VERSION_NUMBER >= 0x10100000L /* 1.1.0 */
#   define OPENSSL_VERSION_MAJOR 1
#   define OPENSSL_VERSION_MINOR 1
#  elif OPENSSL_VERSION_NUMBER >= 0x10000000L /* 1.0.0 */
#   define OPENSSL_VERSION_MAJOR 1
#   define OPENSSL_VERSION_MINOR 0
#  else
#   define OPENSSL_VERSION_MAJOR 0
#   define OPENSSL_VERSION_MINOR 9
#  endif
#  define OPENSSL_VERSION_PREREQ(maj,min)                                \
     ((OPENSSL_VERSION_MAJOR << 16) + OPENSSL_VERSION_MINOR >= ((maj) << 16) + (min))
# endif

# include <openssl/engine.h>
#  if OPENSSL_VERSION_PREREQ(3,0)
#   include <openssl/core_names.h>
#   include <openssl/decoder.h>
#   include <openssl/provider.h>
#  elif !OPENSSL_VERSION_PREREQ(1,1)
#   include <openssl/pem.h>
#  endif

# include "mdf_byteswap.h"
# include "mdf.h"
# include "mdf_fields.h"
# include "version.h"
# if _MSC_VER >= 1300
#  define strdup(s) _strdup(s)
# endif
# ifndef SOL_TCP
#  define SOL_TCP IPPROTO_TCP
# endif
# ifndef SOCK_CLOEXEC
#  define SOCK_CLOEXEC 0
# endif

# ifndef MSG_NOSIGNAL
#  define MSG_NOSIGNAL 0
# endif

#define NELEMS(x) ((int)(sizeof x/sizeof x[0]))
#define MAX_RECORDLENGTH (64 * 1024 * 1024)

#define CSTATE_ENCRYPT 1
#define CSTATE_DECRYPT 2
#define CSTATE_INVALID 4 /* prevents mdf_consume() and mdf_message_send() to do anything until the handshake is completed */

#define COPT_NOENCRYPTION 1
#define COPT_ISPIPE 2
#define COPT_HANDLEDELAY 4
#define COPT_SENDDELAY 8
#define COPT_RECVDELAY 16

struct callbacks {
	void *udata_data;
	void *udata_status;
	mdf_data_callback func_data;
	mdf_status_callback func_status;
};

struct crypto {
	uint8_t *master_secret;
	EVP_CIPHER_CTX * restrict cipher_encrypt_ctx;
	EVP_CIPHER_CTX * restrict cipher_decrypt_ctx;
#if OPENSSL_VERSION_PREREQ(3,0)
	EVP_CIPHER *cipher;
	EVP_MAC_CTX *enc_mctx;
	EVP_MAC_CTX *dec_mctx;
	EVP_MD *digest;
#else
	const EVP_MD *digest;
	EVP_MD_CTX * restrict digest_encrypt_ctx;
	EVP_MD_CTX * restrict digest_decrypt_ctx;
#endif
	uint64_t encrypt_cnt;
	uint64_t encrypt_num;
	uint64_t decrypt_cnt;
	uint32_t digests;
	uint32_t ciphers;
	int cstate;
	uint32_t digest_length;
};

struct mdf_s {
	uint64_t bytes_read;
	uint64_t bytes_written;
	uint8_t * restrict data;
	uint8_t * restrict value;
	struct crypto crypto;
	struct templates *templates;
	struct msg_template *template;
	struct callbacks callbacks;
	uint32_t pos; /* points at the current pmap */
	uint32_t fpos; /* points at the current field */
	uint32_t vlen; /* length of value in bytes */
	uint32_t data_pos; /* current decoding position in the read buffer */
	uint32_t data_used; /* current utilization of the read buffer */
	uint32_t data_size; /* total size of the read buffer */
	uint32_t message_len; /* the total message length including any message digest */
	uint32_t message_end; /* points to the last byte of the message (not including the digest) */
	int field_pmap_pos; /* current field position */
	int heartbeat_interval;
# if defined _WIN32 || defined _WIN64
	SOCKET fd;
# else
	int fd;
# endif
	uint16_t in_pmap_pos; /* bit position in the current pmap, 128,96,64,32,16,8,4,1 */
	uint8_t copt;
	uint8_t oldval; /* carries the byte that we zero out when reading messages, which also happens to be he delay byte if we are in delay-mode */
	uint8_t connect_timeout;
	uint8_t heartbeats_sent;
	uint8_t max_missed_heartbeats;
	uint8_t tcp_nodelay;
	_Atomic int slock;
	MDF_ERROR error;
	time_t time_lastupdate;
	int64_t timediff_ns;
	uint64_t template_version;
	struct templates *templates_old;
	char * restrict connected_host;
	char * restrict connected_ip;
	char * restrict bind_address;
#if OPENSSL_VERSION_PREREQ(3,0)
	OSSL_PROVIDER *prov_legacy;
	OSSL_PROVIDER *prov_default;
	EVP_MAC *mac;
#endif
};

int mdf_int_send_rekey (mdf_t handle);
int mdf_int_send_heartbeat_response (mdf_t handle, const uint64_t timestamp, const uint64_t timestamp_rcv);

#endif
