/*
 * Copyright 2024 Vsevolod Stakhov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef RSPAMD_UTIL_H
#define RSPAMD_UTIL_H

#include "config.h"
#include "mem_pool.h"
#include "printf.h"
#include "fstring.h"
#include "addr.h"
#include "str_util.h"

#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif

#include <time.h>

#ifdef __cplusplus
extern "C" {
#endif

struct rspamd_config;

enum rspamd_exception_type {
	RSPAMD_EXCEPTION_NEWLINE = 0,
	RSPAMD_EXCEPTION_URL,
	RSPAMD_EXCEPTION_GENERIC,
};
/**
 * Structure to point exception in text from processing
 */
struct rspamd_process_exception {
	goffset pos;
	unsigned int len;
	gpointer ptr;
	enum rspamd_exception_type type;
};

/**
 * Create generic socket
 * @param af address family
 * @param type socket type
 * @param protocol socket protocol
 * @param async set non-blocking on a socket
 * @return socket FD or -1 in case of error
 */
int rspamd_socket_create(int af, int type, int protocol, gboolean async);

/*
 * Create socket and bind or connect it to specified address and port
 */
int rspamd_socket_tcp(struct addrinfo *, gboolean is_server, gboolean async);

/*
 * Create socket and bind or connect it to specified address and port
 */
int rspamd_socket_udp(struct addrinfo *, gboolean is_server, gboolean async);

/*
 * Create and bind or connect unix socket
 */
int rspamd_socket_unix(const char *,
					   struct sockaddr_un *,
					   int type,
					   gboolean is_server,
					   gboolean async);

/**
 * Make a universal socket
 * @param credits host, ip or path to unix socket
 * @param port port (used for network sockets)
 * @param type type of socket (SO_STREAM or SO_DGRAM)
 * @param async make this socket async
 * @param is_server make this socket as server socket
 * @param try_resolve try name resolution for a socket (BLOCKING)
 */
int rspamd_socket(const char *credits, uint16_t port, int type,
				  gboolean async, gboolean is_server, gboolean try_resolve);


/*
 * Create socketpair
 */
gboolean rspamd_socketpair(int pair[2], int af);

/*
 * Make specified socket non-blocking
 */
int rspamd_socket_nonblocking(int);

/*
 * Make specified socket blocking
 */
int rspamd_socket_blocking(int);

/*
 * Poll a sync socket for specified events
 */
int rspamd_socket_poll(int fd, int timeout, short events);

/*
 * Init signals
 */
#ifdef HAVE_SA_SIGINFO

void rspamd_signals_init(struct sigaction *sa, void (*sig_handler)(int,
																   siginfo_t *,
																   void *));

#else
void rspamd_signals_init(struct sigaction *sa, void (*sig_handler)(int));
#endif

/*
 * Process title utility functions
 */
int rspamd_init_title(rspamd_mempool_t *pool, int argc, char *argv[], char *envp[]);
int rspamd_setproctitle(const char *fmt, ...);

#ifndef HAVE_PIDFILE
/*
 * Pidfile functions from FreeBSD libutil code
 */
typedef struct rspamd_pidfh_s {
	int pf_fd;
#ifdef HAVE_PATH_MAX
	char pf_path[PATH_MAX + 1];
#elif defined(HAVE_MAXPATHLEN)
	char pf_path[MAXPATHLEN + 1];
#else
	char pf_path[1024 + 1];
#endif
	dev_t pf_dev;
	ino_t pf_ino;
} rspamd_pidfh_t;

rspamd_pidfh_t *rspamd_pidfile_open(const char *path,
									mode_t mode,
									pid_t *pidptr);

int rspamd_pidfile_write(rspamd_pidfh_t *pfh);

int rspamd_pidfile_close(rspamd_pidfh_t *pfh);

int rspamd_pidfile_remove(rspamd_pidfh_t *pfh);

#else
typedef struct pidfh rspamd_pidfh_t;
#define rspamd_pidfile_open pidfile_open
#define rspamd_pidfile_write pidfile_write
#define rspamd_pidfile_close pidfile_close
#define rspamd_pidfile_remove pidfile_remove
#endif

/*
 * Replace %r with rcpt value and %f with from value, new string is allocated in pool
 */
char *resolve_stat_filename(rspamd_mempool_t *pool,
							char *pattern,
							char *rcpt,
							char *from);

const char *
rspamd_log_check_time(double start, double end, int resolution);

/*
 * File locking functions
 */
gboolean rspamd_file_lock(int fd, gboolean async);

gboolean rspamd_file_unlock(int fd, gboolean async);

/*
 * Workarounds for older versions of glib
 */
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 22))
void g_ptr_array_unref(GPtrArray *array);
gboolean g_int64_equal(gconstpointer v1, gconstpointer v2);
unsigned int g_int64_hash(gconstpointer v);
#endif
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 14))
void g_queue_clear(GQueue *queue);
#endif
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 32))
void g_queue_free_full(GQueue *queue, GDestroyNotify free_func);
#endif
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 40))
void g_ptr_array_insert(GPtrArray *array, int index_, gpointer data);
#endif
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 30))
GPtrArray *g_ptr_array_new_full(unsigned int reserved_size,
								GDestroyNotify element_free_func);
#endif
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION < 32))
const char *g_environ_getenv(char **envp, const char *variable);
#endif

/*
 * Convert milliseconds to timeval fields
 */
#define msec_to_tv(msec, tv)                       \
	do {                                           \
		(tv)->tv_sec = (msec) / 1000;              \
		(tv)->tv_usec =                            \
			((msec) - (tv)->tv_sec * 1000) * 1000; \
	} while (0)
#define double_to_tv(dbl, tv)                    \
	do {                                         \
		(tv)->tv_sec = (int) (dbl);              \
		(tv)->tv_usec =                          \
			((dbl) - (int) (dbl)) * 1000 * 1000; \
	} while (0)
#define double_to_ts(dbl, ts)            \
	do {                                 \
		(ts)->tv_sec = (int) (dbl);      \
		(ts)->tv_nsec =                  \
			((dbl) - (int) (dbl)) * 1e9; \
	} while (0)
#define tv_to_msec(tv) ((tv)->tv_sec * 1000LLU + (tv)->tv_usec / 1000LLU)
#define tv_to_double(tv) ((double) (tv)->tv_sec + (tv)->tv_usec / 1.0e6)
#define ts_to_usec(ts) ((ts)->tv_sec * 1000000LLU + \
						(ts)->tv_nsec / 1000LLU)
#define ts_to_double(tv) ((double) (tv)->tv_sec + (tv)->tv_nsec / 1.0e9)

/**
 * Try to allocate a file on filesystem (using fallocate or posix_fallocate)
 * @param fd descriptor
 * @param offset offset of file
 * @param len length to allocate
 * @return -1 in case of failure
 */
int rspamd_fallocate(int fd, off_t offset, off_t len);

/**
 * Utils for working with threads to be compatible with all glib versions
 */
typedef struct rspamd_mutex_s {
#if ((GLIB_MAJOR_VERSION == 2) && (GLIB_MINOR_VERSION > 30))
	GMutex mtx;
#else
	GStaticMutex mtx;
#endif
} rspamd_mutex_t;


/**
 * Create new mutex
 * @return mutex or NULL
 */
rspamd_mutex_t *rspamd_mutex_new(void);

/**
 * Lock mutex
 * @param mtx
 */
void rspamd_mutex_lock(rspamd_mutex_t *mtx);

/**
 * Unlock mutex
 * @param mtx
 */
void rspamd_mutex_unlock(rspamd_mutex_t *mtx);

/**
 * Clear rspamd mutex
 * @param mtx
 */
void rspamd_mutex_free(rspamd_mutex_t *mtx);

/**
 * Deep copy of one hash table to another
 * @param src source hash
 * @param dst destination hash
 * @param key_copy_func function called to copy or modify keys (or NULL)
 * @param value_copy_func function called to copy or modify values (or NULL)
 * @param ud user data for copy functions
 */
void rspamd_hash_table_copy(GHashTable *src, GHashTable *dst,
							gpointer (*key_copy_func)(gconstpointer data, gpointer ud),
							gpointer (*value_copy_func)(gconstpointer data, gpointer ud),
							gpointer ud);


/**
 * Read passphrase from tty
 * @param buf buffer to fill with a password
 * @param size size of the buffer
 * @param echo turn echo on or off
 * @param key unused key
 * @return size of password read
 */
#define rspamd_read_passphrase(buf, size, echo, key) (rspamd_read_passphrase_with_prompt("Enter passphrase: ", (buf), (size), (echo), (key)))

/**
 * Read passphrase from tty with prompt
 * @param prompt prompt to use
 * @param buf buffer to fill with a password
 * @param size size of the buffer
 * @param echo turn echo on or off
 * @param key unused key
 * @return
 */
int rspamd_read_passphrase_with_prompt(const char *prompt, char *buf, int size, bool echo, gpointer key);

/**
 * Portably return the current clock ticks as seconds
 * @return
 */
double rspamd_get_ticks(gboolean rdtsc_ok);

/**
 * Portably return the current virtual clock ticks as seconds
 * @return
 */
double rspamd_get_virtual_ticks(void);


/**
 * Return the real timestamp as unixtime
 */
double rspamd_get_calendar_ticks(void);

/**
 * Special utility to help array freeing in rspamd_mempool
 * @param p
 */
void rspamd_ptr_array_free_hard(gpointer p);

/**
 * Special utility to help array freeing in rspamd_mempool
 * @param p
 */
void rspamd_array_free_hard(gpointer p);

/**
 * Special utility to help GString freeing in rspamd_mempool
 * @param p
 */
void rspamd_gstring_free_hard(gpointer p);

/**
 * Special utility to help GError freeing in rspamd_mempool
 * @param p
 */
void rspamd_gerror_free_maybe(gpointer p);

/**
 * Special utility to help GString freeing (without freeing the memory segment) in rspamd_mempool
 * @param p
 */
void rspamd_gstring_free_soft(gpointer p);


/**
 * Returns some statically initialized random hash seed
 * @return hash seed
 */
uint64_t rspamd_hash_seed(void);

/**
 * Returns random hex string of the specified length
 * @param buf
 * @param len
 */
void rspamd_random_hex(char *buf, uint64_t len);

/**
 * Generate a UUID v7 (time-ordered) string.
 * @param uuid_out output buffer for the 36-char UUID string + NUL
 * @param opt_uid_buf if not NULL, filled with hex log UID derived from UUID's random part
 * @param uid_buflen size of opt_uid_buf in bytes (ignored when opt_uid_buf is NULL)
 * @param timestamp wall-clock time as double (e.g. from ev_time())
 * @return number of UID chars written to opt_uid_buf, 0 if opt_uid_buf is NULL,
 *         -1 if uid_buflen is too small
 */
int rspamd_uuid_v7(char uuid_out[37], char *opt_uid_buf, gsize uid_buflen, double timestamp);

/**
 * Patch UUID v7 random portion (bytes 8-15) to match a new log tag.
 * Hashes the tag to derive bytes, preserves timestamp and version/variant bits.
 * @param uuid UUID string to patch in place (36 chars + NUL)
 * @param tag new log tag value
 * @param tag_len length of tag
 */
void rspamd_uuid_v7_patch_uid(char uuid[37], const char *tag, gsize tag_len);

/**
 * Create an exclusively owned shared memory segment using a random name
 * @param pattern pattern to create (should end with some number of X symbols), modified by this function
 * @return opened descriptor or -1 in case of error (errno is set accordingly)
 */
int rspamd_shmem_mkstemp(char *pattern);

/**
 * Return jittered time value
 */
double rspamd_time_jitter(double in, double jitter);

/**
 * Return random double in range [0..1)
 * @return
 */
double rspamd_random_double(void);

/**
 * Return random double in range [0..1) using xoroshiro128+ algorithm (not crypto secure)
 * @return
 */
double rspamd_random_double_fast(void);
double rspamd_random_double_fast_seed(uint64_t *seed);
uint64_t rspamd_random_uint64_fast_seed(uint64_t *seed);
uint64_t rspamd_random_uint64_fast(void);

/**
 * Seed fast rng
 */
void rspamd_random_seed_fast(void);

/**
 * Constant time version of memcmp
 */
gboolean rspamd_constant_memcmp(const void *a, const void *b, gsize len);

/**
 * Open file without following symlinks or special stuff
 * @param fname filename
 * @param oflags open flags
 * @param mode mode to open
 * @return fd or -1 in case of error
 */
int rspamd_file_xopen(const char *fname, int oflags, unsigned int mode,
					  gboolean allow_symlink);

/**
 * Map file without following symlinks or special stuff
 * @param fname filename
 * @param mode mode to open
 * @param size target size (must NOT be NULL)
 * @return pointer to memory (should be freed using munmap) or NULL in case of error
 */
gpointer rspamd_file_xmap(const char *fname, unsigned int mode, gsize *size,
						  gboolean allow_symlink);

/**
 * Map named shared memory segment
 * @param fname filename
 * @param mode mode to open
 * @param size target size (must NOT be NULL); it is set to 0 if the segment is
 *             empty and to `(gsize)-1` for any other error
 * @return pointer to memory (should be freed using munmap) or NULL in case of error
 */
gpointer rspamd_shmem_xmap(const char *fname, unsigned int mode,
						   gsize *size);

/**
 * Normalize probabilities using polynomial function
 * @param x probability (bias .. 1)
 * @return
 */
double rspamd_normalize_probability(double x, double bias);

/**
 * Converts struct tm to time_t
 * @param tm
 * @param tz timezone in format (hours * 100) + minutes
 * @return
 */
uint64_t rspamd_tm_to_time(const struct tm *tm, glong tz);

/**
 * Splits unix timestamp into struct tm using GMT timezone
 * @param ts
 * @param dest
 */
void rspamd_gmtime(int64_t ts, struct tm *dest);

/**
 * Split unix timestamp into struct tm using local timezone
 * @param ts
 * @param dest
 */
void rspamd_localtime(int64_t ts, struct tm *dest);

#define PTR_ARRAY_FOREACH(ar, i, cur) for ((i) = 0; (ar) != NULL && (i) < (ar)->len && (((cur) = (__typeof__(cur)) g_ptr_array_index((ar), (i))) || 1); ++(i))

/**
 * Compresses the input string using gzip+zlib. Old string is replaced and freed
 * if compressed.
 * @param in
 * @return TRUE if a string has been compressed
 */
gboolean rspamd_fstring_gzip(rspamd_fstring_t **in);

/**
 * Compresses the input string using gzip+zlib. Old string is replaced and freed
 * if compressed. If not compressed it is untouched.
 * @param in
 * @return TRUE if a string has been compressed
 */
gboolean rspamd_fstring_gunzip(rspamd_fstring_t **in);

/**
 * Perform globbing searching for the specified path. Allow recursion,
 * returns an error if maximum nesting is reached.
 * @param pattern
 * @param recursive
 * @param err
 * @return GPtrArray of char *, elements are freed when array is freed
 */
GPtrArray *rspamd_glob_path(const char *dir,
							const char *pattern,
							gboolean recursive,
							GError **err);

struct rspamd_counter_data {
	float mean;
	float stddev;
	uint64_t number;
};

/**
 * Sets counter's data using exponential moving average
 * @param cd counter
 * @param value new counter value
 * @param alpha decay coefficient (0..1)
 * @return new counter value
 */
float rspamd_set_counter_ema(struct rspamd_counter_data *cd,
							 float value,
							 float alpha);

/**
 * Sets counter's data using flat moving average
 * @param cd counter
 * @param value new counter value
 * @return new counter value
 */
double rspamd_set_counter(struct rspamd_counter_data *cd,
						  double value);

/**
 * Shuffle elements in an array inplace
 * @param ar
 */
void rspamd_ptr_array_shuffle(GPtrArray *ar);

enum rspamd_pbkdf_version_id {
	RSPAMD_PBKDF_ID_V1 = 1,
	RSPAMD_PBKDF_ID_V2 = 2,
	RSPAMD_PBKDF_ID_MAX
};

struct rspamd_controller_pbkdf {
	const char *name;
	const char *alias;
	const char *description;
	int type; /* enum rspamd_cryptobox_pbkdf_type */
	int id;
	unsigned int complexity;
	gsize salt_len;
	gsize key_len;
};

extern const struct rspamd_controller_pbkdf pbkdf_list[];

/**
 * Sum array of floats using Kahan sum algorithm
 * @param ar
 * @param nelts
 * @return
 */
float rspamd_sum_floats(float *ar, gsize *nelts);

/**
 * Normalize file path removing dot sequences and repeating '/' symbols as
 * per rfc3986#section-5.2
 * @param path
 * @param len
 * @param nlen
 */
void rspamd_normalize_path_inplace(char *path, unsigned int len, gsize *nlen);

/**
 * Per-process memory information collected from OS-specific sources.
 * All fields are in bytes; unsupported fields are left at zero.
 */
struct rspamd_proc_mem_info {
	uint64_t vm_size;   /**< total virtual memory size                       */
	uint64_t vm_rss;    /**< resident set size                                */
	uint64_t vm_data;   /**< data segment size (Linux only)                   */
	uint64_t vm_stack;  /**< stack size (Linux only)                          */
	uint64_t vm_text;   /**< text/code size (Linux only)                      */
	uint64_t vm_lib;    /**< shared library size (Linux only)                 */
	uint64_t vm_pte;    /**< page table entries size (Linux only)             */
	uint64_t rss_anon;  /**< anonymous resident pages (Linux only)            */
	uint64_t rss_file;  /**< file-backed resident pages (Linux only)          */
	uint64_t rss_shmem; /**< shared memory resident pages (Linux only)        */
};

/**
 * Fill the structure with current process memory info using OS-specific APIs.
 * Returns TRUE if at least vm_rss could be obtained. Fields that are not
 * available on the current platform are left set to zero.
 */
gboolean rspamd_get_process_memory_info(struct rspamd_proc_mem_info *info);

#ifdef __cplusplus
}
#endif

#endif
