/* $Id$
 *
 * Copyright (C) 2006-2007 VozTelecom Sistemas S.L
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio 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 General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */


#include <stdlib.h>/*atoi*/
#include <time.h>  /*gettimeofday*/
#include <poll.h>  /*poll*/
#include "ha.h"
#include "seas.h"
#include "../../core/mem/mem.h"		/*pkg_malloc*/
#include "../../core/mem/shm_mem.h" /*shm_malloc*/

/** if any of these global ping vars is set to 0, then
 * this kind of ping is DISABLED
 */
char *jain_ping_config = 0;
int jain_ping_period = 0;
int jain_pings_lost = 0;
int jain_ping_timeout = 0;

char *servlet_ping_config = 0;
int servlet_ping_period = 0;
int servlet_pings_lost = 0;
int servlet_ping_timeout = 0;

int use_ha = 0;
pid_t pinger_pid;

static inline int parse_ping(
		char *string, int *ping_period, int *pings_lost, int *ping_timeout);
static inline int send_ping(struct as_entry *the_as, struct timeval *now);
/**
 * returns:
 * 0 if no High Availability
 * 1 if High Availability
 * -1 if config error
 */
int prepare_ha(void)
{
	use_ha = 0;
	if(!(jain_ping_config || servlet_ping_config)) {
		jain_pings_lost = servlet_pings_lost = 0;
		return 0;
	}
	if(parse_ping(jain_ping_config, &jain_ping_period, &jain_pings_lost,
			   &jain_ping_timeout)
			< 0)
		goto error;
	if(parse_ping(servlet_ping_config, &servlet_ping_period,
			   &servlet_pings_lost, &servlet_ping_timeout)
			< 0)
		goto error;
	LM_DBG("jain: pinging period :%d max pings lost:%d ping timeout:%d\n",
			jain_ping_period, jain_pings_lost, jain_ping_timeout);
	LM_DBG("servlet: pinging period:%d max pings lost:%d ping timeout:%d\n",
			servlet_ping_period, servlet_pings_lost, servlet_ping_timeout);
	use_ha = 1;
	return 1;
error:
	return -1;
}

int print_pingtable(struct ha *ta, int idx, int lock)
{
	int i;
	if(lock)
		lock_get(ta->mutex);
	for(i = 0; i < ta->size; i++) {
		if((ta->begin + ta->count) > ta->size) {
			if((i < ta->begin && i < ((ta->begin + ta->count) % ta->size))
					|| (i >= ta->begin && i < (ta->begin + ta->count)))
				fprintf(stderr, "*");
			else
				fprintf(stderr, "=");
		} else {
			if(i >= ta->begin && i < (ta->begin + ta->count))
				fprintf(stderr, "*");
			else
				fprintf(stderr, "=");
		}
	}
	if(lock)
		lock_release(ta->mutex);
	fprintf(stderr, "\n");
	for(i = 0; i < ta->size; i++)
		if(i == idx)
			fprintf(stderr, "-");
		else
			fprintf(stderr, "%d", i);
	fprintf(stderr, "\n");
	return 0;
}

/**
 * Parses the PING configuration string. Its format is
 * "ping_period:pings_lost:ping_timeout"
 * ping_period : time between pings
 * pings_lost: number of lost pings before failure
 * ping_timeout: time to consider a ping failed
 *
 * returns
 * 0 if config is not set
 * -1 if config is malformed (unable to parse);
 *  1 if config is successfully set
 */
static inline int parse_ping(
		char *string, int *ping_period, int *pings_lost, int *ping_timeout)
{
	char *ping_period_s, *pings_lost_s, *ping_timeout_s;
	ping_period_s = pings_lost_s = ping_timeout_s = (char *)0;

	if(string == 0 || *string == 0) {
		*ping_period = 0;
		*pings_lost = 0;
		*ping_timeout = 0;
		return 0;
	}

	if(((*string) < '0') || ((*string) > '9')) {
		LM_ERR("malformed ping config string. Unparseable :[%s]\n", string);
		return -1;
	}
	ping_period_s = string;
	while(*string) {
		if(*string == ':') {
			*string = 0;
			if(!pings_lost_s && (*(string + 1))) {
				pings_lost_s = string + 1;
			} else if(!ping_timeout_s && (*(string + 1))) {
				ping_timeout_s = string + 1;
			} else {
				LM_ERR("malformed ping config string. Unparseable :[%s]\n",
						string);
				return -1;
			}
		}
		string++;
	}
	if(!(pings_lost_s && ping_timeout_s)) {
		LM_ERR("malformed ping config string. Unparseable :[%s]\n", string);
		return -1;
	}
	*ping_period = atoi(ping_period_s);
	*pings_lost = atoi(pings_lost_s);
	*ping_timeout = atoi(ping_timeout_s);
	if(*ping_period <= 0 || *pings_lost <= 0 || *ping_timeout <= 0) {
		return -1;
	}
	return 1;
}

/**
 * we spawn a pinger process.
 *
 * some day we could spawn a pinger-thread instead of a process..
 *
 * returns:
 * 	-1 on error;
 */
int spawn_pinger(void)
{
	int n, next_jain, next_servlet, timeout;
	struct timeval now, last_jain, last_servlet;
	struct as_entry *as;

	if((pinger_pid = fork()) < 0) {
		LM_ERR("forking failed!\n");
		goto error;
	} else if(pinger_pid > 0) {
		return 0;
	}
	strcpy(whoami, "Pinger Process\n");
	_ksr_is_main = 0;
	is_dispatcher = 0;
	my_as = 0;
	/* child */
	if(jain_ping_period && servlet_ping_period) {
		next_jain = next_servlet = 0;
	} else if(jain_ping_period) {
		next_servlet = INT_MAX;
		next_jain = 0;
	} else if(servlet_ping_period) {
		next_jain = INT_MAX;
		next_servlet = 0;
	} else {
		next_jain = next_servlet = INT_MAX;
	}

	gettimeofday(&last_jain, NULL);
	memcpy(&last_servlet, &last_jain, sizeof(struct timeval));

	while(1) {
		gettimeofday(&now, NULL);
		if(next_jain != INT_MAX) {
			next_jain = jain_ping_period
						- ((now.tv_sec - last_jain.tv_sec) * 1000
								+ (now.tv_usec - last_jain.tv_usec) / 1000);
		}
		if(next_servlet != INT_MAX) {
			next_servlet =
					servlet_ping_period
					- ((now.tv_sec - last_servlet.tv_sec) * 1000
							+ (now.tv_usec - last_servlet.tv_usec) / 1000);
		}

		timeout = next_jain < next_servlet ? next_jain : next_servlet;

		if((n = poll(NULL, 0, timeout < 0 ? 0 : timeout)) < 0) {
			LM_ERR("poll returned %d\n", n);
			goto error;
		} else if(n == 0) { /*timeout*/
			gettimeofday(&now, NULL);
			if(jain_ping_period
					&& ((now.tv_sec - last_jain.tv_sec) * 1000
							   + (now.tv_usec - last_jain.tv_usec) / 1000)
							   >= jain_ping_period) {
				gettimeofday(&last_jain, NULL);
				for(as = as_list; as; as = as->next) {
					if(as->type == AS_TYPE && as->connected) {
						send_ping(as, &now);
					}
				}
			}
			if(servlet_ping_period
					&& ((now.tv_sec - last_servlet.tv_sec) * 1000
							   + (now.tv_usec - last_servlet.tv_usec) / 1000)
							   >= servlet_ping_period) {
				gettimeofday(&last_servlet, NULL);
				for(as = as_list; as; as = as->next) {
					if(as->type == AS_TYPE && as->connected) {
						send_ping(as, &now);
					}
				}
			}
		} else { /*impossible..*/
			LM_ERR("bug:poll returned %d\n", n);
			goto error;
		}
	}
	return 0;
error:
	return -1;
}

/**
 * sends a ping to the app-server, and uses now as sent time
 *
 * returns
 * 	0 on success
 * 	-1 on error
 */
static inline int send_ping(struct as_entry *the_as, struct timeval *now)
{
	char *the_ping;
	as_msg_p aping;
	int pinglen, retval;
	unsigned int seqno;
	struct ping *pingu;

	aping = (as_msg_p)0;
	the_ping = (char *)0;
	retval = 0;
	if(!(aping = shm_malloc(sizeof(as_msg_t)))) {
		LM_ERR("out of shm_mem for ping event\n");
		retval = -1;
		goto error;
	}
	if(!(the_ping = create_ping_event(&pinglen, 0, &seqno))) {
		LM_ERR("Unable to create ping event\n");
		retval = -1;
		goto error;
	}
	aping->as = the_as;
	aping->msg = the_ping;
	aping->len = pinglen;

	lock_get(the_as->u.as.jain_pings.mutex);
	{
		if(the_as->u.as.jain_pings.count == the_as->u.as.jain_pings.size) {
			LM_ERR("Cant send ping because the pingtable is full (%d pings)\n",
					the_as->u.as.jain_pings.count);
			retval = 0;
			lock_release(the_as->u.as.jain_pings.mutex);
			goto error;
		} else {
			pingu = the_as->u.as.jain_pings.pings + the_as->u.as.jain_pings.end;
			the_as->u.as.jain_pings.end = (the_as->u.as.jain_pings.end + 1)
										  % the_as->u.as.jain_pings.size;
			the_as->u.as.jain_pings.count++;
		}
		memcpy(&pingu->sent, now, sizeof(struct timeval));
		pingu->id = seqno;
	}
	lock_release(the_as->u.as.jain_pings.mutex);
again:
	if(0 > write(write_pipe, &aping, sizeof(as_msg_p))) {
		if(errno == EINTR) {
			goto again;
		} else {
			LM_ERR("error sending ping\n");
			goto error;
		}
	}
	return 0;
error:
	if(aping)
		shm_free(aping);
	if(the_ping)
		shm_free(the_ping);
	return retval;
}

/**
 * Initializes the high availability (ha) structure
 *
 * returns
 * 	0 on success
 * 	-1 on error
 */
int init_pingtable(struct ha *table, int timeout, int maxpings)
{
	if(maxpings <= 0)
		maxpings = 1;
	table->begin = 0;
	table->end = 0;
	table->timed_out_pings = 0;
	table->size = maxpings;
	table->timeout = timeout;

	if(!(table->mutex = lock_alloc())) {
		LM_ERR("Unable to allocate a lock for the ping table\n");
		goto error;
	} else
		lock_init(table->mutex);
	LM_ERR("alloc'ing %d bytes for %d pings\n",
			(int)(maxpings * sizeof(struct ping)), maxpings);
	if(0 == (table->pings = shm_malloc(maxpings * sizeof(struct ping)))) {
		LM_ERR("Unable to shm_malloc %d bytes for %d pings\n",
				(int)(maxpings * sizeof(struct ping)), maxpings);
		goto error;
	} else {
		memset(table->pings, 0, (maxpings * sizeof(struct ping)));
	}
	return 0;
error:
	destroy_pingtable(table);
	return -1;
}

void destroy_pingtable(struct ha *table)
{
	if(table->mutex) {
		lock_dealloc(table->mutex);
		table->mutex = 0;
	}
	if(table->pings) {
		shm_free(table->pings);
		table->pings = 0;
	}
}

/**
 * event_length(4) UNSIGNED INT includes the length 4 bytes itself
 * type(1),
 * processor_id(1), 0 means nobody, 0xFF means everybody, 0<N<0xFF means processor with id=N
 * flags(4),
 * ping_num(4),
 *
 * NOT REENTRANT (uses static local var to store ping seqno.)
 *
 * returns
 * 	0 on error
 * 	pointer to the buffer on success
 *
 */
char *create_ping_event(int *evt_len, int flags, unsigned int *seqno)
{
	unsigned int i, k;
	char *buffer;
	static unsigned int ping_seqno = 0;

	if(!(buffer = shm_malloc(4 + 1 + 1 + 4 + 4))) {
		LM_ERR("out of shm for ping\n");
		return 0;
	}
	*evt_len = (4 + 1 + 1 + 4 + 4);
	ping_seqno++;
	*seqno = ping_seqno;
	i = htonl(14);
	memcpy(buffer, &i, 4);
	k = 4;
	/*type*/
	buffer[k++] = (unsigned char)PING_AC;
	/*processor_id*/
	buffer[k++] = (unsigned char)0xFF;
	/*flags*/
	flags = htonl(flags);
	memcpy(buffer + k, &flags, 4);
	k += 4;
	/*ping sequence number*/
	i = htonl(ping_seqno);
	memcpy(buffer + k, &i, 4);
	k += 4;
	return buffer;
}
